@amsintegra/design-system 1.1.1 → 2.0.1

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/dist/index.js CHANGED
@@ -1,731 +1,2730 @@
1
- import { Button as Button$1, Tag as Tag$1, theme, Card as Card$1, ConfigProvider, App, Pagination as Pagination$1, Switch, Skeleton, Table, Modal, Select, Upload, Breadcrumb } from 'antd';
2
- export { Affix, Alert, Anchor, AutoComplete, Avatar, BackTop, Badge, Breadcrumb, Calendar, Carousel, Cascader, Checkbox, Col, Collapse, ColorPicker, DatePicker, Descriptions, Divider, Drawer, Dropdown, Empty, Flex, FloatButton, Form, Image, Input, InputNumber, Layout, List, Mentions, Menu, Modal, Popconfirm, Popover, Progress, QRCode, Radio, Rate, Result, Row, Segmented, Select, Skeleton, Slider, Space, Spin, Splitter, Statistic, Steps, Switch, Table, Tabs, TimePicker, Timeline, Tooltip, Tour, Transfer, Tree, TreeSelect, Typography, Upload, Watermark } from 'antd';
3
- import { createContext, forwardRef, useContext, useState, useCallback, useMemo, isValidElement, cloneElement, useEffect } from 'react';
1
+ import { createContext, forwardRef, useContext, createElement, useState, useCallback, useEffect, useRef, useId, useLayoutEffect, useMemo, Children, isValidElement } from 'react';
4
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { DownloadOutlined, BulbOutlined, BulbFilled, InboxOutlined } from '@ant-design/icons';
6
- export { CheckCircleOutlined, CheckOutlined, CloseCircleOutlined, CloseOutlined, DeleteOutlined, DownOutlined, DownloadOutlined, EditOutlined, ExclamationCircleOutlined, EyeInvisibleOutlined, EyeOutlined, FileExcelOutlined, FileOutlined, FilePdfOutlined, FilterOutlined, FolderOutlined, InfoCircleOutlined, LeftOutlined, LoadingOutlined, LogoutOutlined, MenuOutlined, PlusOutlined, ReloadOutlined, RightOutlined, SearchOutlined, SettingOutlined, UpOutlined, UploadOutlined, UserOutlined, WarningOutlined } from '@ant-design/icons';
3
+ import { XCircle, AlertTriangle, CheckCircle2, Clock, Circle, Ellipsis, PlusCircle, KeyRound, GripVertical, Trash2, Search, Bell, Loader2, X, LogOut, Columns3, EyeOff, Eye, Hash, FileText, Activity, Tag, CalendarRange, CalendarClock, Building2, Wallet, User, Copy, Rows2, Rows4, Download, UserX, Timer, Pencil, Mail, FileSpreadsheet, Filter, Sparkles, Save, PauseCircle, Info, LogIn, UserPlus, ShieldAlert, Lock, Coins, Boxes, Gauge, TrendingUp, Link2, ShieldCheck, BarChart3, History, PanelLeftClose, SlidersHorizontal, Users, PanelLeftOpen, MailPlus, Briefcase, BookOpen, Menu, BellRing, Package, Layers, Palette, ArrowUp, ArrowDown, ArrowUpDown, ArrowLeft, ArrowRight, RotateCcw, PlayCircle, RefreshCw, Ban, Check, BellOff, FolderOpen, Calendar, Table2, LayoutGrid } from 'lucide-react';
4
+ import { createPortal } from 'react-dom';
7
5
 
8
- // src/theme/AmsConfigProvider.tsx
9
-
10
- // src/theme/tokens.ts
11
- var primitives = {
12
- brandRed: "#A72432",
13
- brandRedActive: "#8B1D28",
14
- // darken 10%
15
- brandRedLight: "#f1faff",
16
- green: "#50cd89",
17
- greenActive: "#47be7d",
18
- greenLight: "#e8fff3",
19
- purple: "#7239ea",
20
- purpleActive: "#5014d0",
21
- purpleLight: "#f8f5ff",
22
- pink: "#f1416c",
23
- pinkActive: "#d9214e",
24
- pinkLight: "#fff5f8",
25
- yellow: "#ffc700",
26
- yellowActive: "#f1bc00",
27
- yellowLight: "#fff8dd",
28
- white: "#ffffff",
29
- gray: {
30
- 100: "#f9f9f9",
31
- 200: "#f4f4f4",
32
- 300: "#e1e3ea",
33
- 400: "#b5b5c3",
34
- 500: "#a1a5b7",
35
- 600: "#7e8299",
36
- 700: "#5e6278",
37
- 800: "#3f4254",
38
- 900: "#181c32"
39
- },
40
- background: {
41
- app: "#f5f8fa",
42
- body: "#ffffff",
43
- appDark: "#151521",
44
- bodyDark: "#1e1e2d"
45
- },
46
- dark: {
47
- base: "#181c32",
48
- bg: "#151521",
49
- header: "#1e1e2d",
50
- active: "#2a2a3c",
51
- separator: "#393945",
52
- // Dark-mode surfaces (from mf-container's antdTheme).
53
- inputBg: "#1b1b29",
54
- cardBg: "#1e1e2d",
55
- // Dark-mode gray scale — DISTINCT from the light `gray` scale (Metronic).
56
- gray: {
57
- 100: "#2b2b40",
58
- 200: "#323248",
59
- 300: "#3b3b64",
60
- 400: "#474761",
61
- 500: "#565674",
62
- 600: "#6d6d80",
63
- 700: "#92929f",
64
- 800: "#cdcdde"
65
- }
66
- }
67
- };
68
- var radius = {
69
- sm: 2,
70
- base: 4,
71
- md: 6,
72
- lg: 8,
73
- xl: 12
6
+ // src/kit/lib/tema.ts
7
+ var NOMBRE_PRODUCTO_DEFAULT = "Backoffice";
8
+ var TEMA_DEFAULT = {
9
+ logo_url: null,
10
+ color_primario: "#A72432",
11
+ color_primario_oscuro: "#8B1D28",
12
+ color_acento: "#C9414D",
13
+ color_peligro: "#A72432",
14
+ color_fondo: "#ffffff",
15
+ color_texto: "#0b1f33",
16
+ color_borde: "#e2e8f0",
17
+ badge_exito: "#e6f4ea",
18
+ badge_advertencia: "#fdf1de",
19
+ badge_error: "#fbe9e9",
20
+ badge_neutro: "#ececec"
74
21
  };
75
- var typography = {
76
- fontFamily: "Inter, Helvetica, Arial, sans-serif",
77
- fontSizeBase: 13,
78
- weight: {
79
- light: 300,
80
- normal: 400,
81
- medium: 500,
82
- semibold: 600,
83
- bold: 700
22
+ function variablesDeTema(tema) {
23
+ return {
24
+ ...tema.color_acento ? { "--tema-acento": tema.color_acento } : {},
25
+ // El spinner de marca se pinta con el logo; sin logo rige el icono neutro de `globals.css`.
26
+ ...tema.logo_url ? { "--tema-logo-spinner": `url("${tema.logo_url}")` } : {},
27
+ "--tema-primario": tema.color_primario,
28
+ "--tema-primario-oscuro": tema.color_primario_oscuro,
29
+ "--tema-peligro": tema.color_peligro,
30
+ "--tema-fondo": tema.color_fondo,
31
+ "--tema-texto": tema.color_texto,
32
+ "--tema-borde": tema.color_borde,
33
+ "--tema-badge-exito": tema.badge_exito,
34
+ "--tema-badge-advertencia": tema.badge_advertencia,
35
+ "--tema-badge-error": tema.badge_error,
36
+ "--tema-badge-neutro": tema.badge_neutro
37
+ };
38
+ }
39
+ var LinkComponentContext = createContext(null);
40
+ var LinkComponentProvider = LinkComponentContext.Provider;
41
+ var LinkBase = forwardRef(function LinkBase2({ href, children, ...rest }, ref) {
42
+ const Injected = useContext(LinkComponentContext);
43
+ if (Injected) {
44
+ return createElement(Injected, { href, children, ...rest });
45
+ }
46
+ return /* @__PURE__ */ jsx("a", { ref, href, ...rest, children });
47
+ });
48
+ var ListadoNavContext = createContext(null);
49
+ var ListadoNavProvider = ListadoNavContext.Provider;
50
+ function useNavegarListado() {
51
+ const ctx = useContext(ListadoNavContext);
52
+ if (!ctx) {
53
+ throw new Error(
54
+ "useNavegarListado must be used within a <ListadoNavProvider>. Wrap your listing screen and provide { ir, pendiente, params } connected to your router or local state."
55
+ );
84
56
  }
57
+ return ctx;
58
+ }
59
+ var TAMANO_BADGE = 16;
60
+ var TAMANO_BOTON = 20;
61
+ var ICONO_DEFAULT = Circle;
62
+ var ICONO_NAV_DASHBOARD = Activity;
63
+ var ICONO_NAV_SALUD = Gauge;
64
+ var ICONO_NAV_ANALITICA = BarChart3;
65
+ var ICONO_NAV_ENTIDADES = Users;
66
+ var ICONO_NAV_INVITACIONES = MailPlus;
67
+ var ICONO_NAV_ETIQUETAS = Tag;
68
+ var ICONO_NAV_JOBS = Briefcase;
69
+ var ICONO_NAV_TEMA = Palette;
70
+ var ICONO_NAV_CONFIGURACION = SlidersHorizontal;
71
+ var ICONO_NAV_API_KEYS = KeyRound;
72
+ var ICONO_NAV_BITACORA = History;
73
+ var ICONO_NAV_NOTIFICACIONES = BellRing;
74
+ var ICONO_NAV_MANUAL = BookOpen;
75
+ var ICONO_NAV_CLIENTE = KeyRound;
76
+ var ICONO_NAV_EXTRACCIONES = FileSpreadsheet;
77
+ var ICONO_CERRAR_SESION = LogOut;
78
+ var ICONO_INICIAR_SESION = LogIn;
79
+ var ICONO_NAV_COLAPSAR = PanelLeftClose;
80
+ var ICONO_NAV_EXPANDIR = PanelLeftOpen;
81
+ var ICONO_NAV_MENU_MOVIL = Menu;
82
+ var ICONO_CERRAR = X;
83
+ var ICONO_ALTA = PlusCircle;
84
+ var ICONO_GENERAR = Sparkles;
85
+ var ICONO_GUARDAR = Save;
86
+ var ICONO_EDITAR = Pencil;
87
+ var ICONO_BAJA = Trash2;
88
+ var ICONO_DESHABILITAR = UserX;
89
+ var ICONO_INACTIVAR = PauseCircle;
90
+ var ICONO_REACTIVAR_ETIQUETA = PlayCircle;
91
+ var ICONO_REACTIVAR = RotateCcw;
92
+ var ICONO_RESTABLECER = RotateCcw;
93
+ var ICONO_REINTENTAR = RefreshCw;
94
+ var ICONO_COPIAR = Copy;
95
+ var ICONO_LINK = Link2;
96
+ var ICONO_DESCARGAR = Download;
97
+ var ICONO_EXPORTAR = FileSpreadsheet;
98
+ var ICONO_EXPORTAR_CSV = Download;
99
+ var ICONO_CONFIRMAR = CheckCircle2;
100
+ var ICONO_VOLVER = ArrowLeft;
101
+ var ICONO_VER_TODOS = ArrowRight;
102
+ var ICONO_ROTAR_KEY = RotateCcw;
103
+ var ICONO_REVOCAR_KEY = Ban;
104
+ var ICONO_API_KEY = KeyRound;
105
+ var ICONO_EMAIL = Mail;
106
+ var ICONO_INVITACION_GENERICA = UserPlus;
107
+ var ICONO_SEGURIDAD = ShieldCheck;
108
+ var ICONO_CAMPANA = Bell;
109
+ var ICONO_SILENCIAR = BellOff;
110
+ var ICONO_FILTRO = Filter;
111
+ var ICONO_BUSCAR = Search;
112
+ var ICONO_LIMPIAR = X;
113
+ var ICONO_SELECCIONADO = Check;
114
+ var ICONO_ORDENAR_ASC = ArrowUp;
115
+ var ICONO_ORDENAR_DESC = ArrowDown;
116
+ var ICONO_ORDENAR_NEUTRO = ArrowUpDown;
117
+ var ICONO_PAGINA_ANTERIOR = ArrowLeft;
118
+ var ICONO_PAGINA_SIGUIENTE = ArrowRight;
119
+ var ICONO_VISTA_TABLA = Table2;
120
+ var ICONO_VISTA_TARJETAS = LayoutGrid;
121
+ var ICONO_ARRASTRAR_COLUMNA = GripVertical;
122
+ var ICONO_COLUMNAS = Columns3;
123
+ var ICONO_COLUMNA_VISIBLE = Eye;
124
+ var ICONO_COLUMNA_OCULTA = EyeOff;
125
+ var ICONO_ACCIONES_FILA = Ellipsis;
126
+ var ICONO_DENSIDAD_COMPACTA = Rows4;
127
+ var ICONO_DENSIDAD_COMODA = Rows2;
128
+ var ICONO_CARGANDO = Loader2;
129
+ var ICONO_INFO_EXPLICACION = Info;
130
+ var ICONO_COL_CODIGO = Hash;
131
+ var ICONO_COL_NOMBRE = Building2;
132
+ var ICONO_COL_ESTADO = Activity;
133
+ var ICONO_COL_MECANISMO = KeyRound;
134
+ var ICONO_COL_TOTAL = Wallet;
135
+ var ICONO_COL_FECHA = CalendarRange;
136
+ var ICONO_COL_FECHA_HORA = CalendarClock;
137
+ var ICONO_COL_ETIQUETA = Tag;
138
+ var ICONO_COL_USUARIO = User;
139
+ var ICONO_COL_DOCUMENTO = FileText;
140
+ var ICONO_PERIODO = CalendarRange;
141
+ var ICONO_VIGENCIA = Calendar;
142
+ var ICONO_DURACION = Timer;
143
+ var ICONO_UBICACION = FolderOpen;
144
+ var ICONOS_ESTATUS = {
145
+ pendiente: Circle,
146
+ procesando: Clock,
147
+ hecha: CheckCircle2,
148
+ gap: AlertTriangle,
149
+ fallida: XCircle
85
150
  };
86
- var sizing = {
87
- controlHeight: 32,
88
- buttonMinWidth: 110
151
+ function iconoDeEstatus(estado) {
152
+ return ICONOS_ESTATUS[estado] ?? ICONO_DEFAULT;
153
+ }
154
+ var ICONOS_SEMAFORO = {
155
+ verde: CheckCircle2,
156
+ ambar: AlertTriangle,
157
+ rojo: XCircle
89
158
  };
90
- var semantic = {
91
- colorPrimary: primitives.brandRed,
92
- colorPrimaryActive: primitives.brandRedActive,
93
- colorSuccess: primitives.green,
94
- colorInfo: primitives.purple,
95
- colorError: primitives.pink,
96
- colorWarning: primitives.yellow,
97
- // Links. Metronic inherits link color from the (red) primary, which reads as
98
- // "danger" in dense UIs. The AMS default uses a neutral dark gray so links
99
- // don't look like errors; consumers can override `colorLink`/`colorLinkHover`
100
- // via AmsConfigProvider's `theme` prop (e.g. back to the brand red or blue).
101
- colorLink: primitives.gray[800],
102
- colorLinkHover: primitives.brandRed,
103
- colorLinkActive: primitives.brandRedActive,
104
- colorBgLayout: primitives.background.app,
105
- colorBgContainer: primitives.background.body,
106
- borderRadius: radius.base,
107
- fontFamily: typography.fontFamily,
108
- fontSize: typography.fontSizeBase,
109
- controlHeight: sizing.controlHeight
159
+ var ETIQUETAS_SEMAFORO = {
160
+ verde: "verde (en linea)",
161
+ ambar: "ambar (advertencia)",
162
+ rojo: "rojo (critico)"
110
163
  };
164
+ function iconoDeSemaforo(color) {
165
+ return ICONOS_SEMAFORO[color] ?? Circle;
166
+ }
167
+ function etiquetaSemaforo(color) {
168
+ return ETIQUETAS_SEMAFORO[color] ?? color;
169
+ }
170
+ var ICONO_AVISO = AlertTriangle;
171
+ var ICONO_ERROR = XCircle;
172
+ var ICONO_OK = CheckCircle2;
173
+ var ICONO_KPI_RITMO = TrendingUp;
174
+ var ICONO_KPI_RENDIMIENTO = Gauge;
175
+ var ICONO_KPI_CONTEO = Boxes;
176
+ var ICONO_KPI_CREDENCIALES = KeyRound;
177
+ var ICONO_KPI_ALERTA = ShieldAlert;
178
+ var ICONO_KPI_CONSUMO = Coins;
179
+ var ICONO_KPI_BLOQUEO = Lock;
180
+ var ICONO_NAV_PLANES = Layers;
181
+ var ICONO_NAV_PAQUETES = Package;
182
+ var ICONO_NAV_ACCESOS = ShieldCheck;
183
+ var ICONO_NAV_USUARIOS = Users;
184
+ var ICONO_LOGIN_MICROSOFT = LogIn;
111
185
 
112
- // src/theme/theme.ts
113
- var sharedToken = {
114
- colorPrimary: semantic.colorPrimary,
115
- colorSuccess: semantic.colorSuccess,
116
- colorInfo: semantic.colorInfo,
117
- colorError: semantic.colorError,
118
- colorWarning: semantic.colorWarning,
119
- colorLink: semantic.colorLink,
120
- colorLinkHover: semantic.colorLinkHover,
121
- colorLinkActive: semantic.colorLinkActive,
122
- borderRadius: semantic.borderRadius,
123
- fontFamily: semantic.fontFamily,
124
- fontSize: semantic.fontSize,
125
- controlHeight: semantic.controlHeight
126
- };
127
- var lightToken = {
128
- ...sharedToken,
129
- colorBgLayout: semantic.colorBgLayout,
130
- colorBgContainer: semantic.colorBgContainer,
131
- colorBgElevated: primitives.background.body,
132
- colorText: primitives.gray[900],
133
- colorTextSecondary: primitives.gray[700],
134
- colorTextTertiary: primitives.gray[500],
135
- colorTextQuaternary: primitives.gray[400],
136
- colorBorder: primitives.gray[300],
137
- colorBorderSecondary: primitives.gray[200]
138
- };
139
- var darkToken = {
140
- ...sharedToken,
141
- colorBgLayout: primitives.background.appDark,
142
- colorBgContainer: primitives.background.bodyDark,
143
- colorBgElevated: primitives.background.bodyDark,
144
- colorText: "#ffffff",
145
- colorTextHeading: "#ffffff",
146
- colorTextSecondary: primitives.dark.gray[800],
147
- colorTextTertiary: primitives.dark.gray[700],
148
- colorTextQuaternary: primitives.dark.gray[600],
149
- colorBorder: primitives.dark.gray[300],
150
- colorBorderSecondary: primitives.dark.gray[200],
151
- boxShadow: "0 6px 16px 0 rgba(0, 0, 0, 0.48), 0 3px 6px -4px rgba(0, 0, 0, 0.36), 0 9px 28px 8px rgba(0, 0, 0, 0.15)"
152
- };
153
- var sharedComponents = {
154
- Button: { controlHeight: 32 },
155
- Tabs: {
156
- itemColor: primitives.gray[600],
157
- itemSelectedColor: semantic.colorPrimary,
158
- itemHoverColor: semantic.colorPrimary
159
- }
160
- };
161
- var lightComponents = {
162
- ...sharedComponents,
163
- Table: {
164
- headerBg: primitives.gray[100],
165
- headerColor: primitives.gray[800],
166
- borderColor: primitives.gray[300],
167
- cellPaddingBlock: 8,
168
- cellPaddingInline: 8
169
- }
170
- };
171
- var darkComponents = {
172
- ...sharedComponents,
173
- Table: {
174
- headerBg: primitives.dark.gray[200],
175
- headerColor: primitives.dark.gray[800],
176
- borderColor: primitives.dark.gray[400],
177
- rowHoverBg: primitives.dark.gray[200],
178
- cellPaddingBlock: 8,
179
- cellPaddingInline: 8
186
+ // src/kit/lib/explicaciones.ts
187
+ var EXPLICACIONES = {
188
+ // ───────────────────────────── plataforma.* (usados por los componentes del kit) ─────────────────────────────
189
+ "plataforma.rol": {
190
+ titulo: "Rol del usuario",
191
+ que_es: "Tu nivel de acceso en este sistema, por ejemplo administrador, operador o consulta.",
192
+ como_se_calcula: "Se resuelve al iniciar sesi\xF3n seg\xFAn tu asignaci\xF3n; no se calcula.",
193
+ de_donde_sale: "Asignaci\xF3n de rol del usuario en el directorio de acceso.",
194
+ como_leerlo: "Determina qu\xE9 m\xF3dulos ves y qu\xE9 acciones puedes ejecutar en cada tabla."
195
+ },
196
+ "plataforma.listado_resultados": {
197
+ titulo: "Resultados del listado",
198
+ que_es: "Cu\xE1ntas filas cumplen la b\xFAsqueda y los filtros actuales frente al total del listado.",
199
+ como_se_calcula: "Se cuentan las filas cuyo texto contiene lo buscado, sin distinguir may\xFAsculas ni acentos, y que cumplen cada filtro. La tabla muestra una p\xE1gina de 25, 50 o 100 de ellas.",
200
+ de_donde_sale: "Lista que ya trae la pantalla; la b\xFAsqueda, los filtros, el orden y la p\xE1gina viajan en la direcci\xF3n (URL).",
201
+ como_leerlo: "Si dice N en total no hay filtro activo. Copiar la direcci\xF3n comparte la misma vista."
202
+ },
203
+ "plataforma.tabla_columnas": {
204
+ titulo: "Columnas de la tabla",
205
+ que_es: "Lista para mostrar u ocultar las columnas de esta tabla que no est\xE1n fijas. Las columnas de identidad y la de acciones siempre se ven.",
206
+ como_se_calcula: "Cada casilla desmarcada oculta esa columna en pantalla; no cambia los datos, el orden, los filtros ni lo que se exporta.",
207
+ de_donde_sale: "Preferencia guardada en este navegador, por tabla y por usuario. Restablecer la borra y vuelve a mostrar todas las columnas con la densidad c\xF3moda.",
208
+ como_leerlo: "Si falta una columna que esperabas, revisa esta lista. Con el almacenamiento del navegador bloqueado (modo privado) la elecci\xF3n dura solo mientras la p\xE1gina siga abierta."
209
+ },
210
+ "plataforma.tabla_densidad": {
211
+ titulo: "Densidad de la tabla",
212
+ que_es: "Alto de las filas: compacta muestra m\xE1s filas por pantalla; c\xF3moda deja m\xE1s aire entre ellas.",
213
+ como_se_calcula: "Compacta reduce el relleno vertical de cada celda; no cambia el texto ni las columnas.",
214
+ de_donde_sale: "Preferencia guardada en este navegador, por tabla y por usuario, junto con las columnas ocultas.",
215
+ como_leerlo: "La tabla se desplaza dentro de su propio marco: la barra horizontal queda al pie de la pantalla, el encabezado arriba y las columnas de identidad y acciones fijas a los lados. Shift + rueda del rat\xF3n desplaza a lo ancho."
216
+ },
217
+ // ───────────────────────────── ejemplos del Backoffice (examples/) ─────────────────────────────
218
+ "inicio.planes_activos": {
219
+ titulo: "Planes activos",
220
+ que_es: "N\xFAmero de planes del cat\xE1logo que hoy se pueden contratar.",
221
+ como_se_calcula: "Conteo de planes con estado Activo; los borradores y los archivados no cuentan.",
222
+ de_donde_sale: "Cat\xE1logo de planes del backoffice (ejemplo con datos ficticios).",
223
+ como_leerlo: "Un plan archivado deja de contar aunque tenga clientes vigentes; esos clientes conservan su plan hasta renovar."
224
+ },
225
+ "inicio.paquetes": {
226
+ titulo: "Paquetes",
227
+ que_es: "N\xFAmero de paquetes de consumo definidos en el cat\xE1logo.",
228
+ como_se_calcula: "Conteo de paquetes registrados, sin importar su estado.",
229
+ de_donde_sale: "Cat\xE1logo de paquetes del backoffice (ejemplo con datos ficticios).",
230
+ como_leerlo: "Un paquete puede estar asociado a varios planes; aqu\xED se cuenta una sola vez."
180
231
  },
181
- Input: {
182
- colorBgContainer: primitives.dark.inputBg,
183
- colorBorder: primitives.dark.gray[400],
184
- colorText: "#ffffff"
232
+ "inicio.accesos_vigentes": {
233
+ titulo: "Accesos vigentes",
234
+ que_es: "Usuarios con acceso al backoffice que no est\xE1n suspendidos ni vencidos.",
235
+ como_se_calcula: "Conteo de usuarios con estado Activo y fecha de vencimiento futura o sin vencimiento.",
236
+ de_donde_sale: "Registro de accesos del backoffice (ejemplo con datos ficticios).",
237
+ como_leerlo: "Un usuario con rol de solo consulta cuenta igual que uno administrador."
185
238
  },
186
- Select: {
187
- colorBgContainer: primitives.dark.inputBg,
188
- colorBorder: primitives.dark.gray[400],
189
- colorText: "#ffffff"
239
+ "inicio.cobertura": {
240
+ titulo: "Cobertura del cat\xE1logo",
241
+ que_es: "Porcentaje de planes que ya tienen al menos un paquete asignado.",
242
+ como_se_calcula: "Planes con al menos un paquete entre planes activos, por 100.",
243
+ de_donde_sale: "Relaci\xF3n plan-paquete del cat\xE1logo (ejemplo con datos ficticios).",
244
+ como_leerlo: "Menos de 100 % indica planes sin paquete: no se pueden vender hasta que se les asigne uno."
190
245
  },
191
- DatePicker: {
192
- colorBgContainer: primitives.dark.inputBg
246
+ "inicio.resumen_catalogo": {
247
+ titulo: "Resumen del cat\xE1logo",
248
+ que_es: "Planes agrupados por estado, con el total de paquetes que suman.",
249
+ como_se_calcula: "Cada plan cae en un solo estado; los paquetes de una fila son la suma de los paquetes de sus planes.",
250
+ de_donde_sale: "Cat\xE1logo de planes y paquetes del backoffice (ejemplo con datos ficticios).",
251
+ como_leerlo: "Un paquete compartido por dos planes se cuenta una vez por cada plan."
193
252
  },
194
- Card: {
195
- colorBgContainer: primitives.dark.cardBg,
196
- colorBorderSecondary: primitives.dark.cardBg
253
+ "planes.col_codigo": {
254
+ titulo: "C\xF3digo",
255
+ que_es: "Identificador corto y estable del plan.",
256
+ como_se_calcula: "Se captura al crear el plan; no se calcula ni se puede repetir.",
257
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
258
+ como_leerlo: "Se usa en integraciones y reportes; el nombre comercial puede cambiar, el c\xF3digo no."
197
259
  },
198
- Modal: {
199
- contentBg: primitives.dark.gray[100],
200
- headerBg: primitives.dark.gray[100]
260
+ "planes.col_nombre": {
261
+ titulo: "Nombre",
262
+ que_es: "Nombre comercial del plan tal como lo ve el cliente.",
263
+ como_se_calcula: "Se captura al crear o editar el plan; no se calcula.",
264
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
265
+ como_leerlo: "La b\xFAsqueda ignora may\xFAsculas y acentos."
201
266
  },
202
- Drawer: {
203
- colorBgElevated: primitives.dark.gray[100]
267
+ "planes.col_precio": {
268
+ titulo: "Precio mensual",
269
+ que_es: "Precio de lista del plan por mes, en pesos, sin impuestos.",
270
+ como_se_calcula: "Se captura al crear o editar el plan; no se calcula.",
271
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
272
+ como_leerlo: "Es precio de lista: un contrato puede tener un descuento que no se refleja aqu\xED."
204
273
  },
205
- Notification: {
206
- colorBgElevated: primitives.dark.gray[100]
274
+ "planes.col_estado": {
275
+ titulo: "Estado del plan",
276
+ que_es: "Situaci\xF3n del plan en el cat\xE1logo: Activo, Borrador o Archivado.",
277
+ como_se_calcula: "Lo fija el administrador al publicar o archivar; no se calcula.",
278
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
279
+ como_leerlo: "Solo los planes Activos se pueden contratar. Archivado conserva el historial pero no admite altas nuevas."
207
280
  },
208
- Message: {
209
- contentBg: primitives.dark.gray[100]
281
+ "planes.col_actualizado": {
282
+ titulo: "Actualizado",
283
+ que_es: "Fecha y hora de la \xFAltima modificaci\xF3n del plan, en tu hora local.",
284
+ como_se_calcula: "Se registra en UTC al guardar cambios y el navegador la convierte a tu zona horaria.",
285
+ de_donde_sale: "Bit\xE1cora de cambios del cat\xE1logo (ejemplo con datos ficticios).",
286
+ como_leerlo: "Cambiar solo el estado tambi\xE9n actualiza esta fecha."
210
287
  },
211
- Menu: {
212
- itemBg: primitives.background.bodyDark,
213
- itemHoverBg: primitives.dark.gray[100],
214
- itemSelectedBg: primitives.dark.active,
215
- itemSelectedColor: semantic.colorPrimary,
216
- itemActiveBg: primitives.dark.active,
217
- subMenuItemBg: primitives.background.bodyDark
288
+ "planes.estado_activo": {
289
+ titulo: "Activo",
290
+ que_es: "El plan est\xE1 publicado y se puede contratar.",
291
+ como_se_calcula: "Estado asignado al publicar el plan.",
292
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
293
+ como_leerlo: "Para retirarlo del cat\xE1logo, arch\xEDvalo; no lo borres."
218
294
  },
219
- Dropdown: {
220
- colorBgElevated: primitives.background.bodyDark,
221
- controlItemBgHover: primitives.dark.gray[100]
295
+ "planes.estado_borrador": {
296
+ titulo: "Borrador",
297
+ que_es: "El plan se est\xE1 preparando y a\xFAn no es visible para contratar.",
298
+ como_se_calcula: "Estado inicial de todo plan nuevo.",
299
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
300
+ como_leerlo: "Publica el plan cuando tenga precio y al menos un paquete."
301
+ },
302
+ "planes.estado_archivado": {
303
+ titulo: "Archivado",
304
+ que_es: "El plan se retir\xF3 del cat\xE1logo; conserva su historial.",
305
+ como_se_calcula: "Estado asignado al archivar un plan activo.",
306
+ de_donde_sale: "Cat\xE1logo de planes (ejemplo con datos ficticios).",
307
+ como_leerlo: "No admite altas nuevas; los clientes existentes conservan su plan hasta renovar."
222
308
  }
223
309
  };
224
- function buildTheme(mode = "light") {
225
- const isDark = mode === "dark";
310
+ function explicacionDe(id) {
311
+ const entrada = EXPLICACIONES[id];
312
+ if (!entrada) throw new Error(`Explicaci\xF3n desconocida: ${id}`);
313
+ return entrada;
314
+ }
315
+ function verificarCatalogo() {
316
+ const problemas = [];
317
+ for (const [id, e] of Object.entries(EXPLICACIONES)) {
318
+ if (!/^[a-z0-9_]+\.[a-z0-9_]+$/.test(id)) problemas.push(`${id}: id invalido`);
319
+ for (const [campo, texto] of Object.entries(e)) {
320
+ if (texto.length > 320) problemas.push(`${id}.${campo}: mas de 320 caracteres`);
321
+ if (/["\\\n]/.test(texto)) problemas.push(`${id}.${campo}: comillas dobles, barra invertida o salto de linea`);
322
+ }
323
+ }
324
+ return problemas;
325
+ }
326
+
327
+ // src/kit/lib/listado.ts
328
+ var TAMANOS_LISTADO = [25, 50, 100];
329
+ var TAMANO_LISTADO_DEFAULT = 25;
330
+ function normalizarTexto(s) {
331
+ return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
332
+ }
333
+ function primero(v) {
334
+ return (Array.isArray(v) ? v[0] : v) ?? "";
335
+ }
336
+ function leerListado(sp, opc) {
337
+ const pagina = Number.parseInt(primero(sp.pagina), 10);
338
+ const tamano = Number.parseInt(primero(sp.tamano), 10);
339
+ const orden = primero(sp.orden);
340
+ const dir = primero(sp.dir);
341
+ const filtros = {};
342
+ for (const clave of opc.filtros) {
343
+ const valor = primero(sp[clave]).trim();
344
+ if (valor) filtros[clave] = valor;
345
+ }
226
346
  return {
227
- algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
228
- token: isDark ? darkToken : lightToken,
229
- components: isDark ? darkComponents : lightComponents
347
+ q: primero(sp.q).trim(),
348
+ pagina: Number.isFinite(pagina) && pagina >= 1 ? pagina : 1,
349
+ tamano: TAMANOS_LISTADO.includes(tamano) ? tamano : TAMANO_LISTADO_DEFAULT,
350
+ orden: opc.ordenes.includes(orden) ? orden : opc.ordenDefault,
351
+ dir: dir === "asc" || dir === "desc" ? dir : opc.dirDefault ?? "asc",
352
+ filtros
230
353
  };
231
354
  }
232
- var amsTheme = buildTheme("light");
233
- var amsThemeDark = buildTheme("dark");
234
- var STORAGE_KEY = "ams-theme";
235
- function getInitialThemeMode(defaultMode = "light") {
236
- if (typeof window === "undefined") return defaultMode;
237
- const saved = window.localStorage.getItem(STORAGE_KEY);
238
- if (saved === "light" || saved === "dark") return saved;
239
- if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) return "dark";
240
- return defaultMode;
241
- }
242
- function applyThemeModeToDOM(mode) {
243
- if (typeof document === "undefined") return;
244
- document.documentElement.setAttribute("data-theme", mode);
245
- document.body?.setAttribute("data-theme", mode);
246
- }
247
- var ThemeModeContext = createContext(null);
248
- function useThemeMode() {
249
- const ctx = useContext(ThemeModeContext);
250
- if (!ctx) {
251
- throw new Error("useThemeMode must be used within an AmsConfigProvider");
355
+ function ausente(v) {
356
+ return v === void 0 || v === null || v === "" || typeof v === "number" && Number.isNaN(v);
357
+ }
358
+ function aplicarListado(items, estado, def) {
359
+ const aguja = normalizarTexto(estado.q);
360
+ const filtrados = items.filter(
361
+ (it) => (!aguja || normalizarTexto(def.texto(it)).includes(aguja)) && (!def.filtrar || def.filtrar(it, estado.filtros))
362
+ );
363
+ const valor = def.valoresOrden[estado.orden];
364
+ if (valor) {
365
+ const factor = estado.dir === "asc" ? 1 : -1;
366
+ filtrados.sort((a, b) => {
367
+ const va = valor(a);
368
+ const vb = valor(b);
369
+ if (ausente(va) && ausente(vb)) return 0;
370
+ if (ausente(va)) return 1;
371
+ if (ausente(vb)) return -1;
372
+ if (typeof va === "number" && typeof vb === "number") return (va - vb) * factor;
373
+ return String(va).localeCompare(String(vb), "es", { sensitivity: "base" }) * factor;
374
+ });
252
375
  }
253
- return ctx;
376
+ const totalPaginas = Math.max(1, Math.ceil(filtrados.length / estado.tamano));
377
+ const pagina = Math.min(estado.pagina, totalPaginas);
378
+ const inicio = (pagina - 1) * estado.tamano;
379
+ return {
380
+ itemsPagina: filtrados.slice(inicio, inicio + estado.tamano),
381
+ totalFiltrado: filtrados.length,
382
+ total: items.length,
383
+ pagina,
384
+ totalPaginas
385
+ };
254
386
  }
255
- function useThemeModeState(controlledMode, defaultMode, persist) {
256
- const isControlled = controlledMode !== void 0;
257
- const [internalMode, setInternalMode] = useState(
258
- () => isControlled ? controlledMode : persist ? getInitialThemeMode(defaultMode) : defaultMode
387
+ function hrefListado(ruta, actual, cambios) {
388
+ const params = new URLSearchParams(actual);
389
+ if (!("pagina" in cambios)) params.delete("pagina");
390
+ for (const [clave, valor] of Object.entries(cambios)) {
391
+ if (valor === null || valor === void 0 || valor === "" || clave === "pagina" && valor === "1") {
392
+ params.delete(clave);
393
+ } else {
394
+ params.set(clave, valor);
395
+ }
396
+ }
397
+ const qs = params.toString();
398
+ return qs ? `${ruta}?${qs}` : ruta;
399
+ }
400
+
401
+ // src/kit/lib/formato.ts
402
+ var NO_DISPONIBLE = "\u2014";
403
+ var DECIMAL = /^(-?)(\d+)(?:\.(\d+))?$/;
404
+ function partes(texto) {
405
+ const m = DECIMAL.exec(texto.trim());
406
+ return m ? { signo: m[1], entero: m[2], fraccion: m[3] ?? "" } : null;
407
+ }
408
+ var miles = (entero) => entero.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
409
+ function formatoMxn(valor) {
410
+ if (valor == null) return NO_DISPONIBLE;
411
+ const p = partes(valor);
412
+ if (!p) return valor;
413
+ return `${p.signo}$${miles(p.entero)}${p.fraccion ? `.${p.fraccion}` : ""}`;
414
+ }
415
+ function formatoNumero(valor) {
416
+ if (valor == null) return NO_DISPONIBLE;
417
+ const p = partes(valor);
418
+ if (!p) return valor;
419
+ return `${p.signo}${miles(p.entero)}${p.fraccion ? `.${p.fraccion}` : ""}`;
420
+ }
421
+ function formatoEntero(valor) {
422
+ if (valor == null) return NO_DISPONIBLE;
423
+ return formatoNumero(String(Math.trunc(valor)));
424
+ }
425
+ function formatoPct(razon) {
426
+ if (razon == null) return NO_DISPONIBLE;
427
+ const p = partes(razon);
428
+ if (!p) return razon;
429
+ const fraccion = p.fraccion.padEnd(4, "0");
430
+ const entero = (p.entero + fraccion.slice(0, 2)).replace(/^0+(?=\d)/, "");
431
+ const resto = fraccion.slice(2);
432
+ const decimales = resto.length > 2 ? resto : resto.padEnd(2, "0");
433
+ return `${p.signo}${miles(entero)}.${decimales} %`;
434
+ }
435
+ function formatoDias(valor) {
436
+ return valor == null ? NO_DISPONIBLE : `${formatoNumero(valor)} d\xEDas`;
437
+ }
438
+ function formatoFecha(iso) {
439
+ if (!iso) return NO_DISPONIBLE;
440
+ return iso.slice(0, 10).replace(/-/g, "/");
441
+ }
442
+ function formatoMes(mes) {
443
+ return mes ? mes.slice(0, 7).replace(/-/g, "/") : NO_DISPONIBLE;
444
+ }
445
+
446
+ // src/kit/lib/fecha.ts
447
+ function formatearFechaHora(iso) {
448
+ if (!iso) return "\u2014";
449
+ const tieneZona = /(Z|[+-]\d{2}:?\d{2})$/.test(iso);
450
+ if (tieneZona && /T/.test(iso)) {
451
+ const d = new Date(iso);
452
+ if (!Number.isNaN(d.getTime())) {
453
+ const p = (n) => String(n).padStart(2, "0");
454
+ return `${d.getFullYear()}/${p(d.getMonth() + 1)}/${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
455
+ }
456
+ }
457
+ const conHora = /T/.test(iso) ? iso : `${iso}T00:00:00`;
458
+ const m = conHora.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/);
459
+ if (!m) return iso;
460
+ const [, anio, mes, dia, hora, min, seg] = m;
461
+ return `${anio}/${mes}/${dia} ${hora}:${min}:${seg}`;
462
+ }
463
+ function formatearFechaHoraCDMX(iso) {
464
+ if (!iso) return "\u2014";
465
+ if (/(Z|[+-]\d{2}:?\d{2})$/.test(iso) && /T/.test(iso)) {
466
+ const d = new Date(iso);
467
+ if (!Number.isNaN(d.getTime())) {
468
+ const partes2 = Object.fromEntries(
469
+ new Intl.DateTimeFormat("en-CA", {
470
+ timeZone: "America/Mexico_City",
471
+ year: "numeric",
472
+ month: "2-digit",
473
+ day: "2-digit",
474
+ hour: "2-digit",
475
+ minute: "2-digit",
476
+ second: "2-digit",
477
+ hourCycle: "h23"
478
+ }).formatToParts(d).map((x) => [x.type, x.value])
479
+ );
480
+ return `${partes2.year}/${partes2.month}/${partes2.day} ${partes2.hour}:${partes2.minute}:${partes2.second}`;
481
+ }
482
+ }
483
+ return formatearFechaHora(iso);
484
+ }
485
+ function formatearRangoFechaHora(desde, hasta) {
486
+ return `${formatearFechaHora(desde)} \u2014 ${formatearFechaHora(hasta)}`;
487
+ }
488
+
489
+ // src/kit/lib/bytes.ts
490
+ function formatearBytes(n) {
491
+ if (n === null || n === void 0 || !Number.isFinite(n) || n < 0) return "\u2014";
492
+ if (n < 1024) return `${n} B`;
493
+ const unidades = ["KB", "MB", "GB", "TB"];
494
+ let valor = n / 1024;
495
+ let i = 0;
496
+ while (valor >= 1024 && i < unidades.length - 1) {
497
+ valor /= 1024;
498
+ i += 1;
499
+ }
500
+ return `${valor.toFixed(1)} ${unidades[i]}`;
501
+ }
502
+ function SpinnerMarca({
503
+ tamano = 120,
504
+ etiqueta = "Cargando..."
505
+ }) {
506
+ const alto = Math.round(tamano * 52 / 120);
507
+ return /* @__PURE__ */ jsxs(
508
+ "div",
509
+ {
510
+ className: "inline-flex flex-col items-center gap-2",
511
+ role: "status",
512
+ "aria-live": "polite",
513
+ "aria-label": etiqueta,
514
+ children: [
515
+ /* @__PURE__ */ jsxs("div", { className: "spinner-marca", style: { width: tamano, height: alto }, children: [
516
+ /* @__PURE__ */ jsx("span", { className: "spinner-marca__base", "aria-hidden": "true" }),
517
+ /* @__PURE__ */ jsx("span", { className: "spinner-marca__fill", "aria-hidden": "true" })
518
+ ] }),
519
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-[#807f7f]", children: etiqueta })
520
+ ]
521
+ }
259
522
  );
260
- const mode = isControlled ? controlledMode : internalMode;
261
- useEffect(() => {
262
- applyThemeModeToDOM(mode);
263
- }, [mode]);
264
- const setMode = useCallback(
265
- (next) => {
266
- if (!isControlled) {
267
- setInternalMode(next);
268
- if (persist && typeof window !== "undefined") {
269
- window.localStorage.setItem(STORAGE_KEY, next);
270
- }
523
+ }
524
+ function OverlayCargando({
525
+ etiqueta = "Procesando...",
526
+ visible = true
527
+ }) {
528
+ if (!visible) return null;
529
+ return /* @__PURE__ */ jsx(
530
+ "div",
531
+ {
532
+ className: "fixed inset-0 z-[60] flex items-center justify-center bg-white/70 backdrop-blur-[1px]",
533
+ role: "alertdialog",
534
+ "aria-busy": "true",
535
+ "aria-label": etiqueta,
536
+ children: /* @__PURE__ */ jsx(SpinnerMarca, { tamano: 140, etiqueta })
537
+ }
538
+ );
539
+ }
540
+ function useAccionConSpinner() {
541
+ const [ejecutando, setEjecutando] = useState(false);
542
+ const [etiqueta, setEtiqueta] = useState("Procesando...");
543
+ const ejecutar = useCallback(
544
+ async (fn, etiquetaAccion = "Procesando...") => {
545
+ setEtiqueta(etiquetaAccion);
546
+ setEjecutando(true);
547
+ try {
548
+ return await fn();
549
+ } finally {
550
+ setEjecutando(false);
271
551
  }
272
552
  },
273
- [isControlled, persist]
553
+ []
274
554
  );
275
- const toggle = useCallback(() => {
276
- setMode(mode === "light" ? "dark" : "light");
277
- }, [mode, setMode]);
278
- return useMemo(
279
- () => ({ mode, setMode, toggle, isDark: mode === "dark" }),
280
- [mode, setMode, toggle]
555
+ const Overlay = useCallback(
556
+ () => /* @__PURE__ */ jsx(OverlayCargando, { visible: ejecutando, etiqueta }),
557
+ [ejecutando, etiqueta]
281
558
  );
559
+ return { ejecutando, ejecutar, Overlay };
560
+ }
561
+
562
+ // src/kit/lib/preferenciasTabla.ts
563
+ var VACIAS = { orden: [], anchos: {}, ocultas: [] };
564
+ function esNavegador() {
565
+ return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
566
+ }
567
+ function cargarPreferencias(clave) {
568
+ if (!esNavegador()) return { ...VACIAS };
569
+ const crudo = window.localStorage.getItem(clave);
570
+ if (!crudo) return { ...VACIAS };
571
+ try {
572
+ const p = JSON.parse(crudo);
573
+ return {
574
+ orden: Array.isArray(p.orden) ? p.orden : [],
575
+ anchos: p.anchos && typeof p.anchos === "object" ? p.anchos : {},
576
+ ocultas: Array.isArray(p.ocultas) ? p.ocultas : []
577
+ };
578
+ } catch {
579
+ return { ...VACIAS };
580
+ }
581
+ }
582
+ function guardarPreferencias(clave, prefs) {
583
+ if (!esNavegador()) return;
584
+ try {
585
+ window.localStorage.setItem(clave, JSON.stringify(prefs));
586
+ } catch {
587
+ }
282
588
  }
283
- function mergeTheme(base, override) {
284
- if (!override) return base;
589
+ function borrarPreferencias(clave) {
590
+ if (!esNavegador()) return;
591
+ window.localStorage.removeItem(clave);
592
+ }
593
+ function ordenarColumnas(idsDefault, ordenGuardado) {
594
+ if (ordenGuardado.length === 0) return [...idsDefault];
595
+ const disponibles = new Set(idsDefault);
596
+ const delGuardado = ordenGuardado.filter((id) => disponibles.has(id));
597
+ const yaColocadas = new Set(delGuardado);
598
+ const nuevas = idsDefault.filter((id) => !yaColocadas.has(id));
599
+ return [...delGuardado, ...nuevas];
600
+ }
601
+ function moverColumna(ids, desde, hasta) {
602
+ if (desde < 0 || desde >= ids.length || hasta < 0 || hasta >= ids.length || desde === hasta) {
603
+ return [...ids];
604
+ }
605
+ const copia = [...ids];
606
+ const [movido] = copia.splice(desde, 1);
607
+ copia.splice(hasta, 0, movido);
608
+ return copia;
609
+ }
610
+
611
+ // src/kit/lib/useTablaPreferencias.ts
612
+ function useTablaPreferencias(clave, idsDefault) {
613
+ const [prefs, setPrefs] = useState({ orden: [], anchos: {}, ocultas: [] });
614
+ useEffect(() => {
615
+ setPrefs(cargarPreferencias(clave));
616
+ }, [clave]);
617
+ const persistir = useCallback(
618
+ (nuevas) => {
619
+ setPrefs(nuevas);
620
+ guardarPreferencias(clave, nuevas);
621
+ },
622
+ [clave]
623
+ );
624
+ const idsOrdenados = ordenarColumnas(idsDefault, prefs.orden);
625
+ const ocultas = new Set(prefs.ocultas);
626
+ const idsVisibles = idsOrdenados.filter((id) => !ocultas.has(id));
627
+ const mover = useCallback(
628
+ (desde, hasta) => {
629
+ const base = ordenarColumnas(idsDefault, prefs.orden);
630
+ const nuevoOrden = moverColumna(base, desde, hasta);
631
+ persistir({ ...prefs, orden: nuevoOrden });
632
+ },
633
+ [idsDefault, prefs, persistir]
634
+ );
635
+ const redimensionar = useCallback(
636
+ (id, ancho) => {
637
+ const w = Math.max(60, Math.round(ancho));
638
+ persistir({ ...prefs, anchos: { ...prefs.anchos, [id]: w } });
639
+ },
640
+ [prefs, persistir]
641
+ );
642
+ const alternarVisible = useCallback(
643
+ (id) => {
644
+ const set = new Set(prefs.ocultas);
645
+ if (set.has(id)) set.delete(id);
646
+ else set.add(id);
647
+ persistir({ ...prefs, ocultas: [...set] });
648
+ },
649
+ [prefs, persistir]
650
+ );
651
+ const restablecer = useCallback(() => {
652
+ borrarPreferencias(clave);
653
+ setPrefs({ orden: [], anchos: {}, ocultas: [] });
654
+ }, [clave]);
285
655
  return {
286
- ...base,
287
- ...override,
288
- token: { ...base.token, ...override.token },
289
- components: { ...base.components, ...override.components },
290
- // A caller-provided algorithm wins; otherwise keep the mode's algorithm.
291
- algorithm: override.algorithm ?? base.algorithm
656
+ /** Ids en el orden efectivo, SIN las ocultas — para pintar la tabla. */
657
+ idsVisibles,
658
+ /** Ids en el orden efectivo, incluyendo ocultas — para el menu de mostrar/ocultar. */
659
+ idsOrdenados,
660
+ /** Set de ids ocultos. */
661
+ ocultas,
662
+ /** Ancho persistido de una columna, o undefined si no se ha redimensionado. */
663
+ anchoDe: (id) => prefs.anchos[id],
664
+ mover,
665
+ redimensionar,
666
+ alternarVisible,
667
+ restablecer
292
668
  };
293
669
  }
294
- function AmsConfigProvider({
295
- mode,
296
- defaultMode = "light",
297
- persistMode = true,
298
- locale,
299
- theme,
670
+
671
+ // src/kit/lib/posicionPopover.ts
672
+ function calcularPosicionPopover({
673
+ trigger,
674
+ contenido,
675
+ viewport,
676
+ margen = 8,
677
+ separacion = 4,
678
+ anchoPreferido = 360
679
+ }) {
680
+ const anchoMax = Math.min(anchoPreferido, viewport.ancho - 2 * margen);
681
+ const ancho = Math.min(contenido.ancho, anchoMax);
682
+ const left = Math.max(margen, Math.min(trigger.left, viewport.ancho - ancho - margen));
683
+ const espacioAbajo = viewport.alto - trigger.bottom - separacion - margen;
684
+ const espacioArriba = trigger.top - separacion - margen;
685
+ const abajo = contenido.alto <= espacioAbajo || espacioAbajo >= espacioArriba;
686
+ const disponible = Math.max(0, abajo ? espacioAbajo : espacioArriba);
687
+ const alto = Math.min(contenido.alto, disponible);
688
+ const top = abajo ? trigger.bottom + separacion : trigger.top - separacion - alto;
689
+ return { top, left, maxHeight: disponible, anchoMax };
690
+ }
691
+
692
+ // src/kit/ui/clases.ts
693
+ var CLASE_BOTON_PRIMARIO = "inline-flex items-center gap-1.5 rounded-md bg-[var(--tema-texto)] px-4 py-2 text-sm font-medium text-white transition hover:brightness-125 disabled:opacity-50";
694
+ var CLASE_BOTON_PRIMARIO_COMPACTO = "inline-flex items-center gap-1.5 rounded-md bg-[var(--tema-texto)] px-3 py-1.5 text-sm font-medium text-white transition hover:brightness-125 disabled:opacity-50";
695
+ var CLASE_BOTON_SECUNDARIO = "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-borde)] px-3 py-1.5 text-sm font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50";
696
+ var CLASE_BOTON_PEQUENO = "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-borde)] px-2.5 py-1.5 text-xs font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50";
697
+ var CLASE_BOTON_CONTORNO = "rounded-md border border-[var(--tema-primario)] px-3 py-1 text-xs font-medium text-[var(--tema-primario)] transition hover:bg-[#eaf2fb]";
698
+ var CLASE_BOTON_ICONO = "rounded-md p-1 text-[#807f7f] transition hover:bg-[var(--tema-badge-neutro)]";
699
+ var CLASE_BOTON_PELIGRO = "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-peligro)] px-3 py-1.5 text-sm font-medium text-[var(--tema-peligro)] transition hover:bg-[var(--tema-badge-error)] disabled:opacity-50";
700
+ var CLASE_CAMPO = "rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] px-3 py-2 text-sm text-[#3c3c3b] focus:border-[var(--tema-primario)] focus:outline-none disabled:bg-[#f4f8fc] disabled:text-[#807f7f]";
701
+ var CLASE_CAMPO_COMPACTO = "rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] px-2 py-1.5 text-sm text-[#3c3c3b] focus:border-[var(--tema-primario)] focus:outline-none";
702
+ var CLASE_ETIQUETA_CAMPO = "text-sm font-medium text-[#3c3c3b]";
703
+ var CLASE_AYUDA_CAMPO = "text-xs text-[#807f7f]";
704
+ var CLASE_ERROR_CAMPO = "text-sm text-[var(--tema-peligro)]";
705
+ var CLASE_EXITO_CAMPO = "text-sm text-[#1e7a34]";
706
+ var CLASE_TARJETA = "rounded-lg border border-[var(--tema-borde)] p-4";
707
+ var CLASE_TITULO_PAGINA = "flex items-center gap-2 text-xl font-semibold text-[var(--tema-primario-oscuro)]";
708
+ var CLASE_TITULO_SECCION = "text-sm font-semibold text-[var(--tema-primario-oscuro)]";
709
+ var CLASE_TEXTO_ATENUADO = "text-sm text-[#807f7f]";
710
+ function PopoverHover({
711
+ etiqueta,
712
+ disparador,
300
713
  children
301
714
  }) {
302
- const themeMode = useThemeModeState(mode, defaultMode, persistMode);
303
- const resolvedTheme = mergeTheme(buildTheme(themeMode.mode), theme);
304
- return /* @__PURE__ */ jsx(ThemeModeContext.Provider, { value: themeMode, children: /* @__PURE__ */ jsx(ConfigProvider, { theme: resolvedTheme, locale, children: /* @__PURE__ */ jsx(App, { children }) }) });
305
- }
306
- var Button = forwardRef(
307
- function Button2(props, ref) {
308
- return /* @__PURE__ */ jsx(Button$1, { ref, ...props });
309
- }
310
- );
311
- var INTENT_COLOR = {
312
- success: semantic.colorSuccess,
313
- error: semantic.colorError,
314
- warning: semantic.colorWarning,
315
- info: semantic.colorInfo,
316
- primary: semantic.colorPrimary
715
+ const [abierto, setAbierto] = useState(false);
716
+ const [pos, setPos] = useState(null);
717
+ const botonRef = useRef(null);
718
+ const contenidoRef = useRef(null);
719
+ const idContenido = useId();
720
+ const reposicionar = useCallback(() => {
721
+ const trigger = botonRef.current?.getBoundingClientRect();
722
+ if (!trigger) return;
723
+ const medida = contenidoRef.current?.getBoundingClientRect();
724
+ setPos(
725
+ calcularPosicionPopover({
726
+ trigger,
727
+ contenido: { ancho: medida?.width ?? 0, alto: medida?.height ?? 0 },
728
+ viewport: { ancho: window.innerWidth, alto: window.innerHeight }
729
+ })
730
+ );
731
+ }, []);
732
+ function abrir() {
733
+ setAbierto(true);
734
+ }
735
+ useLayoutEffect(() => {
736
+ if (!abierto) {
737
+ setPos(null);
738
+ return;
739
+ }
740
+ reposicionar();
741
+ window.addEventListener("scroll", reposicionar, true);
742
+ window.addEventListener("resize", reposicionar);
743
+ return () => {
744
+ window.removeEventListener("scroll", reposicionar, true);
745
+ window.removeEventListener("resize", reposicionar);
746
+ };
747
+ }, [abierto, reposicionar]);
748
+ useEffect(() => {
749
+ if (!abierto) return;
750
+ function alTeclear(e) {
751
+ if (e.key === "Escape") setAbierto(false);
752
+ }
753
+ document.addEventListener("keydown", alTeclear);
754
+ return () => document.removeEventListener("keydown", alTeclear);
755
+ }, [abierto]);
756
+ return /* @__PURE__ */ jsxs(
757
+ "span",
758
+ {
759
+ className: "inline-flex",
760
+ onMouseEnter: abrir,
761
+ onMouseLeave: () => setAbierto(false),
762
+ children: [
763
+ /* @__PURE__ */ jsx(
764
+ "button",
765
+ {
766
+ ref: botonRef,
767
+ type: "button",
768
+ "aria-label": etiqueta,
769
+ title: etiqueta,
770
+ "aria-expanded": abierto,
771
+ "aria-describedby": abierto ? idContenido : void 0,
772
+ onFocus: abrir,
773
+ onBlur: () => setAbierto(false),
774
+ className: "inline-flex items-center justify-center text-[#807f7f] transition hover:text-[var(--tema-primario)]",
775
+ children: disparador
776
+ }
777
+ ),
778
+ abierto && /* @__PURE__ */ jsx(
779
+ "span",
780
+ {
781
+ ref: contenidoRef,
782
+ id: idContenido,
783
+ role: "tooltip",
784
+ style: {
785
+ position: "fixed",
786
+ top: pos?.top ?? 0,
787
+ left: pos?.left ?? 0,
788
+ // Antes de medir (pos null) se oculta para no parpadear en (0,0).
789
+ visibility: pos ? "visible" : "hidden",
790
+ maxWidth: pos?.anchoMax ?? "min(360px, calc(100vw - 16px))",
791
+ maxHeight: pos?.maxHeight,
792
+ overflowY: "auto"
793
+ },
794
+ className: "z-50 w-max rounded-md border border-[var(--tema-borde)] bg-white p-3 text-left shadow-lg",
795
+ children
796
+ }
797
+ )
798
+ ]
799
+ }
800
+ );
801
+ }
802
+ var PREGUNTAS = [
803
+ ["Qu\xE9 es", "que_es"],
804
+ ["C\xF3mo se calcula", "como_se_calcula"],
805
+ ["De d\xF3nde sale", "de_donde_sale"],
806
+ ["C\xF3mo leerlo", "como_leerlo"]
807
+ ];
808
+ function InfoExplicacion({ id }) {
809
+ const e = explicacionDe(id);
810
+ return /* @__PURE__ */ jsx("span", { "data-explicacion-id": id, className: "inline-flex shrink-0 align-middle", children: /* @__PURE__ */ jsx(
811
+ PopoverHover,
812
+ {
813
+ etiqueta: `Explicaci\xF3n: ${e.titulo}`,
814
+ disparador: /* @__PURE__ */ jsx(ICONO_INFO_EXPLICACION, { size: 14, "aria-hidden": "true", className: "text-[#9a9999]" }),
815
+ children: /* @__PURE__ */ jsxs("span", { className: "block max-w-[18rem] text-xs font-normal normal-case tracking-normal text-[#3c3c3b]", children: [
816
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold text-[var(--tema-primario-oscuro)]", children: e.titulo }),
817
+ PREGUNTAS.map(([pregunta, campo]) => /* @__PURE__ */ jsxs("span", { className: "mt-1.5 block", children: [
818
+ /* @__PURE__ */ jsx("span", { className: "block font-medium text-[#565656]", children: pregunta }),
819
+ /* @__PURE__ */ jsx("span", { className: "block leading-snug", children: e[campo] })
820
+ ] }, campo))
821
+ ] })
822
+ }
823
+ ) });
824
+ }
825
+ var CLASES = {
826
+ exito: "bg-[var(--tema-badge-exito)] text-[#1e6b34]",
827
+ error: "bg-[var(--tema-badge-error)] text-[var(--tema-peligro)]",
828
+ aviso: "bg-[var(--tema-badge-advertencia)] text-[#8a5a00]",
829
+ neutro: "bg-[var(--tema-badge-neutro)] text-[#565656]"
317
830
  };
318
- var Tag = forwardRef(function Tag2({ intent, color, ...rest }, ref) {
319
- const resolvedColor = intent ? INTENT_COLOR[intent] : color;
320
- return /* @__PURE__ */ jsx(Tag$1, { ref, color: resolvedColor, ...rest });
321
- });
322
- function useAmsMessage() {
323
- return App.useApp().message;
324
- }
325
- function useAmsNotification() {
326
- return App.useApp().notification;
327
- }
328
- function buildLocale(labels) {
329
- if (!labels) return void 0;
330
- const locale = {};
331
- if (labels.itemsPerPage !== void 0) locale.items_per_page = labels.itemsPerPage;
332
- if (labels.jumpTo !== void 0) locale.jump_to = labels.jumpTo;
333
- if (labels.jumpToConfirm !== void 0) locale.jump_to_confirm = labels.jumpToConfirm;
334
- if (labels.page !== void 0) locale.page = labels.page;
335
- if (labels.prevPage !== void 0) locale.prev_page = labels.prevPage;
336
- if (labels.nextPage !== void 0) locale.next_page = labels.nextPage;
337
- if (labels.pageSize !== void 0) locale.page_size = labels.pageSize;
338
- return Object.keys(locale).length > 0 ? locale : void 0;
339
- }
340
- function Pagination({
341
- labels,
342
- showTotal,
343
- itemRender,
344
- locale,
345
- ...rest
346
- }) {
347
- const mergedLocale = { ...buildLocale(labels), ...locale };
348
- const resolvedShowTotal = showTotal ?? (labels?.total ? (total, range) => labels.total(total, range) : labels?.totalText ? (total, range) => {
349
- const { prefix, suffix, showRange } = labels.totalText;
350
- const middle = showRange ? `${range[0]}-${range[1]}` : String(total);
351
- return [prefix, middle, suffix].filter(Boolean).join(" ");
352
- } : void 0);
353
- const resolvedItemRender = itemRender ?? (labels?.previousText || labels?.nextText ? (_page, type, element) => {
354
- if (type === "prev" && labels?.previousText !== void 0) return labels.previousText;
355
- if (type === "next" && labels?.nextText !== void 0) return labels.nextText;
356
- return element;
357
- } : void 0);
831
+ function Insignia({
832
+ tono,
833
+ children,
834
+ info,
835
+ icono: Icono
836
+ }) {
837
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 align-middle", children: [
838
+ /* @__PURE__ */ jsxs(
839
+ "span",
840
+ {
841
+ className: `${Icono ? "inline-flex items-center gap-1" : "inline-block"} rounded-full px-2 py-0.5 text-xs font-medium ${CLASES[tono]}`,
842
+ children: [
843
+ Icono && /* @__PURE__ */ jsx(Icono, { size: 12, "aria-hidden": "true" }),
844
+ children
845
+ ]
846
+ }
847
+ ),
848
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
849
+ ] });
850
+ }
851
+ function Tarjeta({
852
+ titulo,
853
+ children,
854
+ nota,
855
+ info
856
+ }) {
857
+ return /* @__PURE__ */ jsxs("section", { className: "rounded-lg border border-[var(--tema-borde)] p-5", children: [
858
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
859
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-[var(--tema-primario-oscuro)]", children: titulo }),
860
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
861
+ ] }),
862
+ nota && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-[#807f7f]", children: nota }),
863
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children })
864
+ ] });
865
+ }
866
+ function Metrica({
867
+ etiqueta,
868
+ valor,
869
+ info
870
+ }) {
871
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-[var(--tema-borde)] px-4 py-3", children: [
872
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
873
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-[#807f7f]", children: etiqueta }),
874
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
875
+ ] }),
876
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-lg font-semibold text-[var(--tema-primario-oscuro)]", children: valor })
877
+ ] });
878
+ }
879
+ function ThInfo({
880
+ info,
881
+ children,
882
+ className,
883
+ derecha,
884
+ scope,
885
+ atributos
886
+ }) {
887
+ return /* @__PURE__ */ jsx("th", { scope, ...atributos, className, children: /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 ${derecha ? "justify-end" : ""}`, children: [
888
+ children,
889
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
890
+ ] }) });
891
+ }
892
+ var TIPOS = {
893
+ error: "rounded-md bg-[var(--tema-badge-error)] px-3 py-2 text-sm text-[var(--tema-peligro)]",
894
+ advertencia: "rounded-md bg-[var(--tema-badge-advertencia)] px-3 py-2 text-xs text-[#8a5a00]",
895
+ neutro: "rounded-lg bg-[var(--tema-badge-neutro)] px-3 py-2 text-sm text-[#565656]",
896
+ exito: "rounded-md bg-[var(--tema-badge-exito)] px-3 py-2 text-sm text-[#1e7a34]"
897
+ };
898
+ function Aviso({
899
+ tipo,
900
+ titulo,
901
+ children,
902
+ info
903
+ }) {
904
+ return /* @__PURE__ */ jsxs("div", { role: tipo === "error" ? "alert" : "status", className: TIPOS[tipo], children: [
905
+ titulo && /* @__PURE__ */ jsxs("strong", { className: "font-semibold", children: [
906
+ titulo,
907
+ " "
908
+ ] }),
909
+ children,
910
+ info && /* @__PURE__ */ jsx("span", { className: "ml-1 align-middle", children: /* @__PURE__ */ jsx(InfoExplicacion, { id: info }) })
911
+ ] });
912
+ }
913
+ function EstadoVacio({ children }) {
914
+ return /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed border-[var(--tema-borde)] px-4 py-6 text-sm text-[#807f7f]", children });
915
+ }
916
+ function EstadoVacioFila({ columnas, children = "Sin resultados" }) {
917
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columnas, className: "px-3 py-6 text-center text-sm text-[#807f7f]", children }) });
918
+ }
919
+ function NoDisponible({ motivo, info }) {
920
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-[var(--tema-borde)] px-4 py-3 text-sm text-[#565656]", children: [
921
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 font-medium", children: [
922
+ "No disponible",
923
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
924
+ ] }),
925
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-[#807f7f]", children: motivo })
926
+ ] });
927
+ }
928
+ function BarraProgreso({ porcentaje, etiqueta }) {
358
929
  return /* @__PURE__ */ jsx(
359
- Pagination$1,
930
+ "div",
360
931
  {
361
- ...rest,
362
- locale: Object.keys(mergedLocale).length > 0 ? mergedLocale : void 0,
363
- showTotal: resolvedShowTotal,
364
- itemRender: resolvedItemRender
932
+ role: "progressbar",
933
+ "aria-label": etiqueta,
934
+ "aria-valuemin": 0,
935
+ "aria-valuemax": 100,
936
+ "aria-valuenow": porcentaje,
937
+ className: "h-1.5 w-full overflow-hidden rounded-full bg-[var(--tema-borde)]",
938
+ children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-[var(--tema-primario)] transition-all", style: { width: `${porcentaje}%` } })
365
939
  }
366
940
  );
367
941
  }
368
- var Card = Card$1;
369
- function ThemeToggle(props) {
370
- const { isDark, toggle } = useThemeMode();
371
- return /* @__PURE__ */ jsx(
372
- Switch,
942
+ function useAccionAviso({
943
+ ejecutar,
944
+ errorGenerico,
945
+ onHecho
946
+ }) {
947
+ const [ejecutando, setEjecutando] = useState(false);
948
+ const [aviso, setAviso] = useState(null);
949
+ async function correr() {
950
+ setEjecutando(true);
951
+ setAviso(null);
952
+ let resultado;
953
+ try {
954
+ resultado = await ejecutar();
955
+ } catch {
956
+ resultado = { tipo: "error", texto: errorGenerico };
957
+ }
958
+ setAviso(resultado);
959
+ setEjecutando(false);
960
+ if (resultado.tipo === "ok") onHecho?.();
961
+ }
962
+ return { ejecutando, aviso, correr };
963
+ }
964
+ function AvisoAccionEstado({
965
+ ejecutando,
966
+ aviso,
967
+ etiquetaSpinner
968
+ }) {
969
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
970
+ ejecutando && /* @__PURE__ */ jsx(SpinnerMarca, { tamano: 32, etiqueta: etiquetaSpinner }),
971
+ aviso && /* @__PURE__ */ jsx(
972
+ "span",
973
+ {
974
+ role: aviso.tipo === "ok" ? "status" : "alert",
975
+ className: aviso.tipo === "ok" ? "text-xs text-[var(--tema-primario-oscuro)]" : "text-xs text-[var(--tema-peligro)]",
976
+ children: aviso.texto
977
+ }
978
+ )
979
+ ] });
980
+ }
981
+ function BotonAccionAviso({
982
+ etiqueta,
983
+ etiquetaEjecutando,
984
+ etiquetaSpinner,
985
+ ariaLabel,
986
+ info,
987
+ errorGenerico,
988
+ ejecutar,
989
+ onHecho
990
+ }) {
991
+ const { ejecutando, aviso, correr } = useAccionAviso({ ejecutar, errorGenerico, onHecho });
992
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [
993
+ /* @__PURE__ */ jsx(
994
+ "button",
995
+ {
996
+ type: "button",
997
+ onClick: correr,
998
+ disabled: ejecutando,
999
+ "aria-label": ariaLabel,
1000
+ className: "rounded-md border border-[var(--tema-borde)] px-2 py-0.5 text-xs font-medium text-[var(--tema-primario-oscuro)] transition hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50",
1001
+ children: ejecutando ? etiquetaEjecutando : etiqueta
1002
+ }
1003
+ ),
1004
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: info }),
1005
+ /* @__PURE__ */ jsx(AvisoAccionEstado, { ejecutando, aviso, etiquetaSpinner })
1006
+ ] });
1007
+ }
1008
+ function EnlaceNav({
1009
+ href,
1010
+ icono,
1011
+ etiqueta
1012
+ }) {
1013
+ return /* @__PURE__ */ jsxs(
1014
+ LinkBase,
373
1015
  {
374
- checked: isDark,
375
- onChange: toggle,
376
- checkedChildren: /* @__PURE__ */ jsx(BulbFilled, {}),
377
- unCheckedChildren: /* @__PURE__ */ jsx(BulbOutlined, {}),
378
- ...props
1016
+ href,
1017
+ "aria-label": etiqueta,
1018
+ title: etiqueta,
1019
+ className: "pulsable flex items-center gap-2.5 rounded-md px-2 py-2 transition hover:bg-[var(--tema-badge-neutro)] hover:text-[var(--tema-primario)] md:group-data-[colapsado=true]/side:justify-center",
1020
+ children: [
1021
+ icono,
1022
+ /* @__PURE__ */ jsx("span", { className: "md:group-data-[colapsado=true]/side:hidden", children: etiqueta })
1023
+ ]
379
1024
  }
380
1025
  );
381
1026
  }
382
- function buildPaginationLocale(labels) {
383
- if (!labels) return void 0;
384
- const locale = {};
385
- if (labels.itemsPerPage !== void 0) locale.items_per_page = labels.itemsPerPage;
386
- if (labels.jumpTo !== void 0) locale.jump_to = labels.jumpTo;
387
- if (labels.jumpToConfirm !== void 0) locale.jump_to_confirm = labels.jumpToConfirm;
388
- if (labels.page !== void 0) locale.page = labels.page;
389
- if (labels.prevPage !== void 0) locale.prev_page = labels.prevPage;
390
- if (labels.nextPage !== void 0) locale.next_page = labels.nextPage;
391
- if (labels.pageSize !== void 0) locale.page_size = labels.pageSize;
392
- return Object.keys(locale).length > 0 ? locale : void 0;
393
- }
394
- var DEFAULT_LABELS = {
395
- emptyText: "No data",
396
- sortAsc: "Sort ascending",
397
- sortDesc: "Sort descending",
398
- cancelSort: "Cancel sort"
399
- };
400
- function toNumericValue(value) {
401
- if (value === null || value === void 0 || value === "") return null;
402
- const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : Number(value);
403
- return Number.isNaN(num) || !Number.isFinite(num) ? null : num;
404
- }
405
- function buildTotalText(labels) {
406
- if (!labels) return void 0;
407
- if (labels.total) return (total, range) => labels.total(total, range);
408
- if (labels.totalText) {
409
- return (total, range) => {
410
- const { prefix, suffix, showRange } = labels.totalText;
411
- const middle = showRange ? `${range[0]}-${range[1]}` : String(total);
412
- return [prefix, middle, suffix].filter(Boolean).join(" ");
1027
+ function SeccionNav({ titulo, children }) {
1028
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1029
+ titulo && /* @__PURE__ */ jsx("span", { className: "px-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-[#9a9a9a] md:group-data-[colapsado=true]/side:hidden", children: titulo }),
1030
+ children
1031
+ ] });
1032
+ }
1033
+
1034
+ // src/kit/lib/panelPrefs.ts
1035
+ function leerPref(clave, porDefecto) {
1036
+ try {
1037
+ const crudo = window.localStorage.getItem(clave);
1038
+ return crudo === null ? porDefecto : JSON.parse(crudo);
1039
+ } catch {
1040
+ return porDefecto;
1041
+ }
1042
+ }
1043
+ function guardarPref(clave, valor) {
1044
+ try {
1045
+ window.localStorage.setItem(clave, JSON.stringify(valor));
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ var CLAVE = "sidebar:colapsado";
1050
+ function SidebarColapsable({
1051
+ cabecera,
1052
+ pie,
1053
+ children
1054
+ }) {
1055
+ const [colapsado, setColapsado] = useState(false);
1056
+ const [abiertoMovil, setAbiertoMovil] = useState(false);
1057
+ useEffect(() => {
1058
+ setColapsado(leerPref(CLAVE, false) === true);
1059
+ }, []);
1060
+ useEffect(() => {
1061
+ if (!abiertoMovil) return;
1062
+ const alTeclear = (e) => {
1063
+ if (e.key === "Escape") setAbiertoMovil(false);
413
1064
  };
1065
+ document.addEventListener("keydown", alTeclear);
1066
+ return () => document.removeEventListener("keydown", alTeclear);
1067
+ }, [abiertoMovil]);
1068
+ function alternar() {
1069
+ const nuevo = !colapsado;
1070
+ setColapsado(nuevo);
1071
+ guardarPref(CLAVE, nuevo);
414
1072
  }
415
- return void 0;
1073
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1074
+ /* @__PURE__ */ jsx(
1075
+ "button",
1076
+ {
1077
+ type: "button",
1078
+ onClick: () => setAbiertoMovil(true),
1079
+ "aria-label": "Abrir men\xFA",
1080
+ title: "Abrir men\xFA",
1081
+ className: "fixed left-3 top-3 z-30 flex h-9 w-9 items-center justify-center rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] text-[#565656] shadow-sm md:hidden",
1082
+ children: /* @__PURE__ */ jsx(ICONO_NAV_MENU_MOVIL, { size: TAMANO_BOTON, "aria-hidden": "true" })
1083
+ }
1084
+ ),
1085
+ abiertoMovil && /* @__PURE__ */ jsx(
1086
+ "button",
1087
+ {
1088
+ type: "button",
1089
+ "aria-label": "Cerrar men\xFA",
1090
+ onClick: () => setAbiertoMovil(false),
1091
+ className: "fixed inset-0 z-30 bg-black/40 md:hidden"
1092
+ }
1093
+ ),
1094
+ /* @__PURE__ */ jsxs(
1095
+ "aside",
1096
+ {
1097
+ "data-colapsado": colapsado ? "true" : "false",
1098
+ "data-abierto-movil": abiertoMovil ? "true" : "false",
1099
+ onClick: (e) => {
1100
+ if (e.target.closest("a")) setAbiertoMovil(false);
1101
+ },
1102
+ className: `group/side fixed inset-y-0 left-0 z-40 flex w-60 shrink-0 flex-col border-r border-[var(--tema-borde)] bg-[var(--tema-fondo)] transition-transform md:sticky md:top-0 md:h-screen md:translate-x-0 md:transition-[width] md:data-[colapsado=true]:w-14 ${abiertoMovil ? "translate-x-0" : "-translate-x-full"}`,
1103
+ children: [
1104
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border-b border-[var(--tema-borde)] px-4 py-4 md:group-data-[colapsado=true]/side:items-center md:group-data-[colapsado=true]/side:px-1", children: [
1105
+ /* @__PURE__ */ jsx("div", { className: "md:group-data-[colapsado=true]/side:hidden", children: cabecera }),
1106
+ /* @__PURE__ */ jsx(
1107
+ "button",
1108
+ {
1109
+ type: "button",
1110
+ onClick: alternar,
1111
+ "aria-label": colapsado ? "Expandir men\xFA" : "Contraer men\xFA",
1112
+ title: colapsado ? "Expandir men\xFA" : "Contraer men\xFA",
1113
+ "aria-expanded": !colapsado,
1114
+ className: "hidden w-fit items-center rounded-md p-1 text-[#807f7f] transition hover:bg-[var(--tema-badge-neutro)] hover:text-[var(--tema-primario)] md:flex",
1115
+ children: colapsado ? /* @__PURE__ */ jsx(ICONO_NAV_EXPANDIR, { size: TAMANO_BOTON, "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ICONO_NAV_COLAPSAR, { size: TAMANO_BOTON, "aria-hidden": "true" })
1116
+ }
1117
+ ),
1118
+ /* @__PURE__ */ jsx(
1119
+ "button",
1120
+ {
1121
+ type: "button",
1122
+ onClick: () => setAbiertoMovil(false),
1123
+ "aria-label": "Cerrar men\xFA lateral",
1124
+ className: "absolute right-2 top-2 rounded-md p-1 text-[#807f7f] md:hidden",
1125
+ children: /* @__PURE__ */ jsx(ICONO_CERRAR, { size: TAMANO_BOTON, "aria-hidden": "true" })
1126
+ }
1127
+ )
1128
+ ] }),
1129
+ /* @__PURE__ */ jsx("nav", { className: "flex flex-1 flex-col gap-6 overflow-y-auto px-3 py-4 text-sm font-medium text-[#565656] md:group-data-[colapsado=true]/side:px-1.5", children }),
1130
+ /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--tema-borde)] px-4 py-3 md:group-data-[colapsado=true]/side:px-1", children: pie })
1131
+ ]
1132
+ }
1133
+ )
1134
+ ] });
416
1135
  }
417
- function buildItemRender(labels) {
418
- if (!labels || labels.previousText === void 0 && labels.nextText === void 0) {
419
- return void 0;
1136
+
1137
+ // src/kit/lib/tablaMarco.ts
1138
+ var PREFS_MARCO_DEFAULT = { ocultas: [], densidad: "comoda" };
1139
+ var ID_SEGURO = /^[A-Za-z0-9_-]+$/;
1140
+ function hashCorto(texto) {
1141
+ let h = 2166136261;
1142
+ for (let i = 0; i < texto.length; i++) {
1143
+ h ^= texto.charCodeAt(i);
1144
+ h = Math.imul(h, 16777619) >>> 0;
420
1145
  }
421
- return (_page, type, element) => {
422
- if (type === "prev" && labels.previousText !== void 0) return labels.previousText;
423
- if (type === "next" && labels.nextText !== void 0) return labels.nextText;
424
- return element;
425
- };
1146
+ return h.toString(36);
1147
+ }
1148
+ function claveMarco(tabla, usuario) {
1149
+ return `tabla.marco.v1.${tabla}.${usuario ? hashCorto(usuario) : "anon"}`;
1150
+ }
1151
+ function cargarPrefsMarco(clave) {
1152
+ try {
1153
+ if (typeof window === "undefined") return { ...PREFS_MARCO_DEFAULT };
1154
+ const crudo = window.localStorage.getItem(clave);
1155
+ if (!crudo) return { ...PREFS_MARCO_DEFAULT };
1156
+ const p = JSON.parse(crudo);
1157
+ return {
1158
+ ocultas: Array.isArray(p.ocultas) ? p.ocultas.filter((x) => typeof x === "string") : [],
1159
+ densidad: p.densidad === "compacta" ? "compacta" : "comoda"
1160
+ };
1161
+ } catch {
1162
+ return { ...PREFS_MARCO_DEFAULT };
1163
+ }
1164
+ }
1165
+ function guardarPrefsMarco(clave, prefs) {
1166
+ try {
1167
+ window.localStorage.setItem(clave, JSON.stringify(prefs));
1168
+ } catch {
1169
+ }
1170
+ }
1171
+ function borrarPrefsMarco(clave) {
1172
+ try {
1173
+ window.localStorage.removeItem(clave);
1174
+ } catch {
1175
+ }
1176
+ }
1177
+ function cssColumnasOcultas(alcance, ocultas) {
1178
+ const seguras = ocultas.filter((id) => ID_SEGURO.test(id));
1179
+ if (seguras.length === 0) return "";
1180
+ const selectores = seguras.map((id) => `[data-marco-tabla="${alcance}"] [data-col="${id}"]`);
1181
+ return `${selectores.join(",")}{display:none}`;
1182
+ }
1183
+ function offsetsAcumulados(anchos) {
1184
+ const offsets = [];
1185
+ let total = 0;
1186
+ for (const ancho of anchos) {
1187
+ offsets.push(total);
1188
+ total += ancho;
1189
+ }
1190
+ return { offsets, total };
1191
+ }
1192
+ var ContextoTablaPlana = createContext(false);
1193
+ function TablaPlana({ children }) {
1194
+ return /* @__PURE__ */ jsx(ContextoTablaPlana.Provider, { value: true, children });
426
1195
  }
427
- function DataTable({
428
- heading,
429
- extra,
430
- loading = false,
431
- labels,
432
- paginationLabels,
433
- firstColumnNumericTag = false,
434
- columns,
435
- dataSource,
436
- scroll,
437
- bordered = true,
438
- pagination,
1196
+ var ContextoUsuarioTablas = createContext(null);
1197
+ function UsuarioTablas({ usuario, children }) {
1198
+ return /* @__PURE__ */ jsx(ContextoUsuarioTablas.Provider, { value: usuario ?? null, children });
1199
+ }
1200
+ var CLASE_BOTON = "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-borde)] px-2 py-1 text-xs font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)]";
1201
+ function MarcoTabla({
1202
+ clave,
1203
+ etiqueta,
1204
+ columnas,
1205
+ barra,
1206
+ alto,
1207
+ ajuste = "acotado",
1208
+ pie,
1209
+ controles = true,
1210
+ className = "",
1211
+ clasePlana = "overflow-x-auto print:overflow-visible",
1212
+ children
1213
+ }) {
1214
+ const plana = useContext(ContextoTablaPlana);
1215
+ if (plana)
1216
+ return pie ? /* @__PURE__ */ jsxs(Fragment, { children: [
1217
+ /* @__PURE__ */ jsx("div", { className: clasePlana, children }),
1218
+ pie
1219
+ ] }) : /* @__PURE__ */ jsx("div", { className: clasePlana, children });
1220
+ return /* @__PURE__ */ jsx(
1221
+ MarcoActivo,
1222
+ {
1223
+ clave,
1224
+ etiqueta,
1225
+ columnas,
1226
+ barra,
1227
+ alto,
1228
+ ajuste,
1229
+ pie,
1230
+ controles,
1231
+ className,
1232
+ children
1233
+ }
1234
+ );
1235
+ }
1236
+ function MarcoActivo({
1237
+ clave,
1238
+ etiqueta,
1239
+ columnas,
1240
+ barra,
1241
+ alto,
1242
+ ajuste,
1243
+ pie,
1244
+ controles,
439
1245
  className,
440
- ...rest
1246
+ children
441
1247
  }) {
442
- const mergedLabels = { ...DEFAULT_LABELS, ...labels };
443
- const resolvedPagination = pagination === false || pagination === void 0 ? pagination : (() => {
444
- const paginationLocale = buildPaginationLocale(paginationLabels);
445
- const totalFn = buildTotalText(paginationLabels);
446
- const itemRenderFn = buildItemRender(paginationLabels);
447
- return {
448
- ...totalFn ? { showTotal: totalFn } : {},
449
- ...itemRenderFn ? { itemRender: itemRenderFn } : {},
450
- ...paginationLocale ? { locale: paginationLocale } : {},
451
- ...pagination
1248
+ const usuario = useContext(ContextoUsuarioTablas);
1249
+ const claveLs = claveMarco(clave, usuario);
1250
+ const alcance = useId();
1251
+ const scroll = useRef(null);
1252
+ const [prefs, setPrefs] = useState(PREFS_MARCO_DEFAULT);
1253
+ const [menu, setMenu] = useState(false);
1254
+ const zonaMenu = useRef(null);
1255
+ useEffect(() => {
1256
+ setPrefs(cargarPrefsMarco(claveLs));
1257
+ }, [claveLs]);
1258
+ const persistir = useCallback(
1259
+ (nuevas) => {
1260
+ setPrefs(nuevas);
1261
+ guardarPrefsMarco(claveLs, nuevas);
1262
+ },
1263
+ [claveLs]
1264
+ );
1265
+ const ocultasValidas = prefs.ocultas.filter((id) => columnas?.some((c) => c.id === id));
1266
+ const css = cssColumnasOcultas(alcance, ocultasValidas);
1267
+ const actualizarSombras = useCallback(() => {
1268
+ const el = scroll.current;
1269
+ if (!el) return;
1270
+ const izq = el.scrollLeft > 0;
1271
+ const der = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
1272
+ el.toggleAttribute("data-sombra-izq", izq);
1273
+ el.toggleAttribute("data-sombra-der", der);
1274
+ }, []);
1275
+ useLayoutEffect(() => {
1276
+ const el = scroll.current;
1277
+ if (!el) return;
1278
+ const medir = () => {
1279
+ const fila = el.querySelector("thead tr");
1280
+ const izq = fila ? Array.from(fila.querySelectorAll(':scope > [data-fija="izq"]')) : [];
1281
+ const { offsets, total } = offsetsAcumulados(izq.map((c) => c.offsetWidth));
1282
+ offsets.forEach((o, i) => el.style.setProperty(`--fija-izq-${i}`, `${o}px`));
1283
+ el.style.setProperty("--fija-izq-total", `${total}px`);
1284
+ el.setAttribute("data-fijas-izq", String(izq.length));
1285
+ const der = fila?.querySelector(':scope > [data-fija="der"]');
1286
+ el.style.setProperty("--fija-der-total", `${der?.offsetWidth ?? 0}px`);
1287
+ actualizarSombras();
452
1288
  };
453
- })();
454
- const resolvedColumns = firstColumnNumericTag && columns && columns.length > 0 ? columns.map((col, index) => {
455
- if (index !== 0) return col;
456
- const originalRender = col.render;
457
- return {
458
- ...col,
459
- render: (value, record, i) => {
460
- const content = originalRender ? originalRender(value, record, i) : value;
461
- const numeric = toNumericValue(value);
462
- if (numeric === null) return content;
463
- return /* @__PURE__ */ jsx(
464
- Tag,
1289
+ medir();
1290
+ if (typeof ResizeObserver === "undefined") return;
1291
+ const obs = new ResizeObserver(medir);
1292
+ obs.observe(el);
1293
+ const tabla = el.querySelector("table");
1294
+ if (tabla) obs.observe(tabla);
1295
+ return () => obs.disconnect();
1296
+ });
1297
+ useEffect(() => {
1298
+ const el = scroll.current;
1299
+ if (!el) return;
1300
+ const alGirar = (e) => {
1301
+ if (!e.shiftKey || e.deltaY === 0 || Math.abs(e.deltaX) > 0) return;
1302
+ if (el.scrollWidth <= el.clientWidth) return;
1303
+ el.scrollLeft += e.deltaY;
1304
+ e.preventDefault();
1305
+ actualizarSombras();
1306
+ };
1307
+ el.addEventListener("wheel", alGirar, { passive: false });
1308
+ return () => el.removeEventListener("wheel", alGirar);
1309
+ }, [actualizarSombras]);
1310
+ useEffect(() => {
1311
+ if (!menu) return;
1312
+ const tecla = (e) => e.key === "Escape" && setMenu(false);
1313
+ const fuera = (e) => {
1314
+ if (zonaMenu.current && !zonaMenu.current.contains(e.target)) setMenu(false);
1315
+ };
1316
+ document.addEventListener("keydown", tecla);
1317
+ document.addEventListener("mousedown", fuera);
1318
+ return () => {
1319
+ document.removeEventListener("keydown", tecla);
1320
+ document.removeEventListener("mousedown", fuera);
1321
+ };
1322
+ }, [menu]);
1323
+ function alternarColumna(id) {
1324
+ const ocultas = prefs.ocultas.includes(id) ? prefs.ocultas.filter((x) => x !== id) : [...prefs.ocultas, id];
1325
+ persistir({ ...prefs, ocultas });
1326
+ }
1327
+ function fijarDensidad(densidad) {
1328
+ persistir({ ...prefs, densidad });
1329
+ }
1330
+ function restablecer() {
1331
+ setPrefs({ ...PREFS_MARCO_DEFAULT });
1332
+ borrarPrefsMarco(claveLs);
1333
+ }
1334
+ const hayColumnas = Boolean(columnas && columnas.length > 0);
1335
+ return /* @__PURE__ */ jsxs(
1336
+ "div",
1337
+ {
1338
+ "data-marco-ajuste": ajuste,
1339
+ className: `flex min-w-0 flex-col gap-2 ${ajuste === "pagina" ? "min-h-0 flex-1" : ""} ${className}`,
1340
+ children: [
1341
+ css && /* @__PURE__ */ jsx("style", { children: css }),
1342
+ (controles || barra) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", "data-marco-barra": "", children: [
1343
+ barra && /* @__PURE__ */ jsx("div", { className: "mr-auto flex flex-wrap items-center gap-2", children: barra }),
1344
+ controles && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
1345
+ hayColumnas && /* @__PURE__ */ jsxs("div", { className: "relative", ref: zonaMenu, children: [
1346
+ /* @__PURE__ */ jsxs(
1347
+ "button",
1348
+ {
1349
+ type: "button",
1350
+ onClick: () => setMenu((v) => !v),
1351
+ "aria-expanded": menu,
1352
+ "aria-haspopup": "true",
1353
+ title: "Mostrar u ocultar columnas",
1354
+ className: CLASE_BOTON,
1355
+ children: [
1356
+ /* @__PURE__ */ jsx(ICONO_COLUMNAS, { size: TAMANO_BADGE, "aria-hidden": "true" }),
1357
+ "Columnas",
1358
+ ocultasValidas.length > 0 && /* @__PURE__ */ jsxs("span", { className: "rounded bg-[var(--tema-badge-neutro)] px-1 tabular-nums", children: [
1359
+ "-",
1360
+ ocultasValidas.length
1361
+ ] })
1362
+ ]
1363
+ }
1364
+ ),
1365
+ menu && /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-full z-30 mt-1 w-60 rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] p-2 shadow-lg", children: [
1366
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Columnas visibles", children: [
1367
+ /* @__PURE__ */ jsx("p", { className: "flex items-center gap-1 px-1 pb-1 text-[10px] font-semibold uppercase tracking-wide text-[#9a9a9a]", children: "Columnas visibles" }),
1368
+ columnas.map((c) => {
1369
+ const idInput = `${alcance}-${c.id}`;
1370
+ return /* @__PURE__ */ jsxs(
1371
+ "label",
1372
+ {
1373
+ htmlFor: idInput,
1374
+ className: "flex cursor-pointer items-center gap-2 rounded px-1.5 py-1 text-sm text-[#3c3c3b] hover:bg-[var(--tema-badge-neutro)]",
1375
+ children: [
1376
+ /* @__PURE__ */ jsx(
1377
+ "input",
1378
+ {
1379
+ id: idInput,
1380
+ type: "checkbox",
1381
+ checked: !prefs.ocultas.includes(c.id),
1382
+ onChange: () => alternarColumna(c.id)
1383
+ }
1384
+ ),
1385
+ c.etiqueta
1386
+ ]
1387
+ },
1388
+ c.id
1389
+ );
1390
+ })
1391
+ ] }),
1392
+ /* @__PURE__ */ jsxs(
1393
+ "button",
1394
+ {
1395
+ type: "button",
1396
+ onClick: restablecer,
1397
+ className: "mt-2 inline-flex w-full items-center justify-center gap-1.5 rounded border border-[var(--tema-borde)] px-2 py-1 text-xs text-[#3c3c3b] hover:bg-[var(--tema-badge-neutro)]",
1398
+ children: [
1399
+ /* @__PURE__ */ jsx(ICONO_RESTABLECER, { size: TAMANO_BADGE, "aria-hidden": "true" }),
1400
+ "Restablecer"
1401
+ ]
1402
+ }
1403
+ )
1404
+ ] })
1405
+ ] }),
1406
+ hayColumnas && /* @__PURE__ */ jsx(InfoExplicacion, { id: "plataforma.tabla_columnas" }),
1407
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center overflow-hidden rounded-md border border-[var(--tema-borde)]", role: "group", "aria-label": "Densidad de filas", children: [
1408
+ /* @__PURE__ */ jsx(BotonDensidad, { actual: prefs.densidad, valor: "compacta", onClick: fijarDensidad }),
1409
+ /* @__PURE__ */ jsx(BotonDensidad, { actual: prefs.densidad, valor: "comoda", onClick: fijarDensidad })
1410
+ ] }),
1411
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: "plataforma.tabla_densidad" })
1412
+ ] })
1413
+ ] }),
1414
+ /* @__PURE__ */ jsx(
1415
+ "div",
465
1416
  {
466
- intent: numeric < 0 ? "error" : "success",
467
- style: { minWidth: 72, textAlign: "center" },
468
- children: content
1417
+ ref: scroll,
1418
+ role: "region",
1419
+ "aria-label": etiqueta,
1420
+ tabIndex: 0,
1421
+ "data-marco-tabla": alcance,
1422
+ "data-densidad": prefs.densidad,
1423
+ onScroll: actualizarSombras,
1424
+ onFocus: (e) => {
1425
+ if (e.target === e.currentTarget) return;
1426
+ const celda = e.target.closest("td,th");
1427
+ celda?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
1428
+ },
1429
+ "data-ajuste": ajuste,
1430
+ style: alto ? { "--marco-alto": alto } : void 0,
1431
+ className: `marco-tabla-scroll rounded-lg border border-[var(--tema-borde)] ${ajuste === "pagina" ? "min-h-28" : ""}`,
1432
+ children
469
1433
  }
470
- );
1434
+ ),
1435
+ pie && // Al cambiar de pagina (o de tamano) el cuerpo vuelve arriba: la pagina ya no se desplaza, la tabla si.
1436
+ /* @__PURE__ */ jsx(
1437
+ "div",
1438
+ {
1439
+ className: "shrink-0",
1440
+ "data-marco-pie": "",
1441
+ onClick: (e) => {
1442
+ if (e.target.closest("button")) scroll.current?.scrollTo?.({ top: 0 });
1443
+ },
1444
+ children: pie
1445
+ }
1446
+ )
1447
+ ]
1448
+ }
1449
+ );
1450
+ }
1451
+ function BotonDensidad({
1452
+ actual,
1453
+ valor,
1454
+ onClick
1455
+ }) {
1456
+ const Icono = valor === "compacta" ? ICONO_DENSIDAD_COMPACTA : ICONO_DENSIDAD_COMODA;
1457
+ const nombre = valor === "compacta" ? "Densidad compacta" : "Densidad c\xF3moda";
1458
+ const activo = actual === valor;
1459
+ return /* @__PURE__ */ jsx(
1460
+ "button",
1461
+ {
1462
+ type: "button",
1463
+ "aria-pressed": activo,
1464
+ "aria-label": nombre,
1465
+ title: nombre,
1466
+ onClick: () => onClick(valor),
1467
+ className: `px-2 py-1 transition ${activo ? "bg-[var(--tema-primario)] text-white" : "text-[#565656] hover:bg-[var(--tema-badge-neutro)]"}`,
1468
+ children: /* @__PURE__ */ jsx(Icono, { size: TAMANO_BADGE, "aria-hidden": "true" })
1469
+ }
1470
+ );
1471
+ }
1472
+ function CascaronApp({
1473
+ producto,
1474
+ hrefInicio = "/",
1475
+ logo,
1476
+ usuario,
1477
+ hrefCerrarSesion = "/auth/logout",
1478
+ grupos,
1479
+ pieExtra,
1480
+ children
1481
+ }) {
1482
+ return /* @__PURE__ */ jsx(UsuarioTablas, { usuario: usuario.idEstable, children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-full", children: [
1483
+ /* @__PURE__ */ jsx(
1484
+ SidebarColapsable,
1485
+ {
1486
+ cabecera: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1487
+ /* @__PURE__ */ jsx(LinkBase, { href: hrefInicio, className: "flex shrink-0 items-center gap-2", children: logo && // eslint-disable-next-line @next/next/no-img-element
1488
+ /* @__PURE__ */ jsx("img", { src: logo.src, alt: logo.alt, width: logo.ancho, height: logo.alto }) }),
1489
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-[var(--tema-primario)]", children: producto })
1490
+ ] }),
1491
+ pie: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 md:group-data-[colapsado=true]/side:flex-col", children: [
1492
+ pieExtra,
1493
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col leading-tight md:group-data-[colapsado=true]/side:hidden", children: [
1494
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] font-medium text-[#3c3c3b]", title: usuario.nombre, children: usuario.nombre }),
1495
+ usuario.rol && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 inline-flex items-center gap-1", children: [
1496
+ /* @__PURE__ */ jsx(
1497
+ "span",
1498
+ {
1499
+ className: "w-fit rounded-full bg-[var(--tema-badge-neutro)] px-1.5 py-0.5 text-[10px] font-medium text-[#565656]",
1500
+ title: `Rol: ${usuario.rol}`,
1501
+ children: usuario.rol
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: "plataforma.rol" })
1505
+ ] })
1506
+ ] }),
1507
+ /* @__PURE__ */ jsx(
1508
+ "a",
1509
+ {
1510
+ href: hrefCerrarSesion,
1511
+ title: "Cerrar sesi\xF3n",
1512
+ "aria-label": "Cerrar sesi\xF3n",
1513
+ className: "flex shrink-0 items-center transition hover:text-[var(--tema-primario)]",
1514
+ children: /* @__PURE__ */ jsx(ICONO_CERRAR_SESION, { size: TAMANO_BOTON, "aria-hidden": "true" })
1515
+ }
1516
+ )
1517
+ ] }),
1518
+ children: grupos.map((g, i) => /* @__PURE__ */ jsx(SeccionNav, { titulo: g.titulo, children: g.enlaces.map(({ href, etiqueta, icono: Icono }) => /* @__PURE__ */ jsx(EnlaceNav, { href, etiqueta, icono: /* @__PURE__ */ jsx(Icono, { size: TAMANO_BOTON, "aria-hidden": "true" }) }, href)) }, g.titulo ?? i))
471
1519
  }
472
- };
473
- }) : columns;
474
- const resolvedScroll = scroll ?? { x: "max-content" };
475
- return /* @__PURE__ */ jsxs("div", { className, children: [
476
- heading ? /* @__PURE__ */ jsx("div", { style: { marginBottom: 8 }, children: /* @__PURE__ */ jsx("h3", { style: { margin: 0, fontWeight: 600 }, children: heading }) }) : null,
477
- extra ? /* @__PURE__ */ jsx("div", { style: { marginBottom: 8 }, children: extra }) : null,
478
- loading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { active: true, paragraph: { rows: 0 }, title: { width: "100%" } }, i)) }) : /* @__PURE__ */ jsx(
479
- Table,
1520
+ ),
1521
+ /* @__PURE__ */ jsx(
1522
+ "div",
480
1523
  {
481
- columns: resolvedColumns,
482
- dataSource,
483
- bordered,
484
- scroll: resolvedScroll,
485
- pagination: resolvedPagination,
486
- locale: {
487
- emptyText: mergedLabels.emptyText,
488
- triggerAsc: mergedLabels.sortAsc,
489
- triggerDesc: mergedLabels.sortDesc,
490
- cancelSort: mergedLabels.cancelSort
491
- },
492
- ...rest
1524
+ "data-area-contenido": "",
1525
+ className: "flex min-h-screen min-w-0 flex-1 flex-col overflow-x-clip pt-14 transition-[padding] md:pt-0 md:pr-[var(--asistente-ancho,0px)]",
1526
+ children
493
1527
  }
494
1528
  )
1529
+ ] }) });
1530
+ }
1531
+ function Migas({
1532
+ hrefVolver,
1533
+ etiquetaVolver,
1534
+ actual,
1535
+ actualMono = false
1536
+ }) {
1537
+ return /* @__PURE__ */ jsxs("nav", { "aria-label": "Ruta", className: "mb-3 flex flex-wrap items-center gap-2 text-xs text-[#807f7f]", children: [
1538
+ /* @__PURE__ */ jsxs(LinkBase, { href: hrefVolver, className: "text-[var(--tema-primario)] underline", children: [
1539
+ "\u2190 ",
1540
+ etiquetaVolver
1541
+ ] }),
1542
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "/" }),
1543
+ /* @__PURE__ */ jsx("span", { className: actualMono ? "font-mono" : void 0, children: actual })
495
1544
  ] });
496
1545
  }
497
- function useControllableState({
498
- value,
499
- defaultValue,
500
- onChange
501
- }) {
502
- const isControlled = value !== void 0;
503
- const [internal, setInternal] = useState(defaultValue);
504
- const current = isControlled ? value : internal;
505
- const setValue = useCallback(
506
- (next) => {
507
- if (!isControlled) {
508
- setInternal(next);
509
- }
510
- onChange?.(next);
1546
+ function SubNav({
1547
+ etiqueta,
1548
+ secciones,
1549
+ activa
1550
+ }) {
1551
+ return /* @__PURE__ */ jsx("nav", { "aria-label": etiqueta, className: "mt-4 flex flex-wrap gap-2 text-sm", children: secciones.map((s) => /* @__PURE__ */ jsx(
1552
+ LinkBase,
1553
+ {
1554
+ href: s.href,
1555
+ "aria-current": s.id === activa ? "page" : void 0,
1556
+ className: `pulsable rounded-md border px-3 py-1 ${s.id === activa ? "border-[var(--tema-primario)] bg-[var(--tema-badge-neutro)] font-semibold" : "border-[var(--tema-borde)] hover:bg-[var(--tema-badge-neutro)]"}`,
1557
+ children: s.texto
511
1558
  },
512
- [isControlled, onChange]
513
- );
514
- return [current, setValue];
1559
+ s.id
1560
+ )) });
515
1561
  }
516
- function isPromise(value) {
517
- return !!value && typeof value.then === "function";
1562
+ var BOTON = {
1563
+ // Primario = relleno neutro oscuro (no el rojo de marca); el rojo queda reservado a peligro.
1564
+ primario: "inline-flex items-center gap-1.5 rounded-md bg-[var(--tema-texto)] px-3 py-1.5 text-sm font-medium text-white transition hover:brightness-125 disabled:opacity-50",
1565
+ secundario: "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-borde)] px-3 py-1.5 text-sm font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50"
1566
+ };
1567
+ function AccionConDialogo({
1568
+ etiqueta,
1569
+ titulo,
1570
+ icono,
1571
+ variante = "secundario",
1572
+ children
1573
+ }) {
1574
+ const [abierto, setAbierto] = useState(false);
1575
+ const disparador = useRef(null);
1576
+ const panel = useRef(null);
1577
+ const tituloId = useId();
1578
+ useEffect(() => {
1579
+ if (!abierto) return;
1580
+ queueMicrotask(() => {
1581
+ const campo = panel.current?.querySelector("input, select, textarea");
1582
+ campo?.focus();
1583
+ });
1584
+ }, [abierto]);
1585
+ function cerrar() {
1586
+ setAbierto(false);
1587
+ disparador.current?.focus();
1588
+ }
1589
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1590
+ /* @__PURE__ */ jsxs("button", { ref: disparador, type: "button", onClick: () => setAbierto(true), className: BOTON[variante], children: [
1591
+ icono,
1592
+ etiqueta
1593
+ ] }),
1594
+ abierto && /* @__PURE__ */ jsx(
1595
+ "div",
1596
+ {
1597
+ "data-testid": "dialogo-fondo",
1598
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
1599
+ onClick: cerrar,
1600
+ children: /* @__PURE__ */ jsxs(
1601
+ "div",
1602
+ {
1603
+ ref: panel,
1604
+ role: "dialog",
1605
+ "aria-modal": "true",
1606
+ "aria-labelledby": tituloId,
1607
+ onClick: (e) => e.stopPropagation(),
1608
+ onKeyDown: (e) => {
1609
+ if (e.key === "Escape") {
1610
+ e.stopPropagation();
1611
+ cerrar();
1612
+ }
1613
+ },
1614
+ className: "max-h-[90vh] w-full max-w-md overflow-y-auto rounded-xl border border-[var(--tema-borde)] bg-[var(--tema-fondo)] p-6 shadow-xl",
1615
+ children: [
1616
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1617
+ /* @__PURE__ */ jsxs("h2", { id: tituloId, className: "flex items-center gap-2 text-base font-semibold text-[var(--tema-primario-oscuro)]", children: [
1618
+ icono,
1619
+ titulo
1620
+ ] }),
1621
+ /* @__PURE__ */ jsx(
1622
+ "button",
1623
+ {
1624
+ type: "button",
1625
+ onClick: cerrar,
1626
+ "aria-label": "Cerrar",
1627
+ title: "Cerrar",
1628
+ className: "rounded-md p-1 text-[#807f7f] transition hover:bg-[var(--tema-badge-neutro)]",
1629
+ children: /* @__PURE__ */ jsx(ICONO_CERRAR, { size: TAMANO_BADGE, "aria-hidden": "true" })
1630
+ }
1631
+ )
1632
+ ] }),
1633
+ /* @__PURE__ */ jsx("div", { className: "mt-4", children })
1634
+ ]
1635
+ }
1636
+ )
1637
+ }
1638
+ )
1639
+ ] });
518
1640
  }
519
- function ModalDialog({
520
- trigger,
521
- children,
522
- open,
523
- defaultOpen = false,
524
- closeOnOk = true,
525
- closeOnCancel = true,
526
- onOk,
527
- onCancel,
528
- onOpenChange,
529
- ...modalProps
530
- }) {
531
- const [isOpen, setOpen] = useControllableState({
532
- value: open,
533
- defaultValue: defaultOpen,
534
- onChange: onOpenChange
535
- });
536
- const handleOpen = useCallback(() => setOpen(true), [setOpen]);
537
- const handleCancel = useCallback(
538
- (event) => {
539
- onCancel?.(event);
540
- if (closeOnCancel) setOpen(false);
541
- },
542
- [onCancel, closeOnCancel, setOpen]
543
- );
544
- const handleOk = useCallback(
545
- async (event) => {
546
- const result = onOk?.(event);
547
- if (!closeOnOk) return;
548
- if (isPromise(result)) {
549
- try {
550
- await result;
551
- } finally {
552
- setOpen(false);
1641
+ var CLASE_REGION_LISTADO = "flex min-h-0 flex-1 flex-col";
1642
+ function PaginaListado({
1643
+ titulo,
1644
+ icono,
1645
+ descripcion,
1646
+ acciones,
1647
+ className = "",
1648
+ children
1649
+ }) {
1650
+ return /* @__PURE__ */ jsxs("main", { "data-pagina-listado": "", className: `flex min-h-0 w-full flex-1 flex-col gap-3 px-6 py-4 ${className}`, children: [
1651
+ /* @__PURE__ */ jsxs("header", { className: "flex shrink-0 flex-wrap items-center gap-x-3 gap-y-1", children: [
1652
+ /* @__PURE__ */ jsxs("h1", { className: "flex shrink-0 items-center gap-2 text-xl font-semibold text-[var(--tema-primario-oscuro)]", children: [
1653
+ icono,
1654
+ titulo
1655
+ ] }),
1656
+ descripcion && /* @__PURE__ */ jsx("p", { className: "min-w-[12rem] flex-1 truncate text-sm text-[#807f7f]", title: descripcion, children: descripcion }),
1657
+ acciones && /* @__PURE__ */ jsx("div", { className: "ml-auto flex shrink-0 flex-wrap items-center gap-2", children: acciones })
1658
+ ] }),
1659
+ children
1660
+ ] });
1661
+ }
1662
+ function ModalPanel({
1663
+ titulo,
1664
+ tituloId,
1665
+ acciones,
1666
+ onCerrar,
1667
+ etiquetaCerrar = "Cerrar",
1668
+ ancho = "max-w-[min(96vw,100rem)]",
1669
+ children
1670
+ }) {
1671
+ const dialogo = useRef(null);
1672
+ useEffect(() => {
1673
+ dialogo.current?.focus();
1674
+ }, []);
1675
+ return /* @__PURE__ */ jsx("div", { "data-testid": "modal-fondo", className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4", onClick: onCerrar, children: /* @__PURE__ */ jsxs(
1676
+ "div",
1677
+ {
1678
+ ref: dialogo,
1679
+ role: "dialog",
1680
+ "aria-modal": "true",
1681
+ "aria-labelledby": tituloId,
1682
+ tabIndex: -1,
1683
+ onClick: (e) => e.stopPropagation(),
1684
+ onKeyDown: (e) => {
1685
+ if (e.key === "Escape") {
1686
+ e.stopPropagation();
1687
+ onCerrar();
553
1688
  }
554
- } else {
555
- setOpen(false);
556
- }
557
- },
558
- [onOk, closeOnOk, setOpen]
559
- );
560
- const triggerNode = useMemo(() => {
561
- if (!trigger) return null;
562
- if (typeof trigger === "function") {
563
- return trigger(handleOpen);
1689
+ },
1690
+ className: `flex max-h-[90dvh] w-full ${ancho} flex-col gap-3 rounded-xl border border-[var(--tema-borde)] bg-[var(--tema-fondo)] p-4 shadow-xl outline-none`,
1691
+ children: [
1692
+ /* @__PURE__ */ jsxs("div", { "data-modal-cabecera": "", className: "flex shrink-0 items-start justify-between gap-3", children: [
1693
+ /* @__PURE__ */ jsx("h2", { id: tituloId, className: "flex min-w-0 items-center gap-1.5 text-base font-semibold text-[var(--tema-primario-oscuro)]", children: titulo }),
1694
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1695
+ acciones,
1696
+ /* @__PURE__ */ jsx(
1697
+ "button",
1698
+ {
1699
+ type: "button",
1700
+ onClick: onCerrar,
1701
+ "aria-label": etiquetaCerrar,
1702
+ title: etiquetaCerrar,
1703
+ className: "rounded-md p-1 text-[#807f7f] transition hover:bg-[var(--tema-badge-neutro)]",
1704
+ children: /* @__PURE__ */ jsx(ICONO_CERRAR, { size: TAMANO_BADGE, "aria-hidden": "true" })
1705
+ }
1706
+ )
1707
+ ] })
1708
+ ] }),
1709
+ /* @__PURE__ */ jsx("div", { "data-modal-cuerpo": "", className: `${CLASE_REGION_LISTADO} gap-3`, children })
1710
+ ]
564
1711
  }
565
- if (isValidElement(trigger)) {
566
- const el = trigger;
567
- const originalOnClick = el.props.onClick;
568
- return cloneElement(el, {
569
- onClick: (event) => {
570
- originalOnClick?.(event);
571
- handleOpen();
1712
+ ) });
1713
+ }
1714
+ function Pestanas({
1715
+ etiqueta,
1716
+ pestanas,
1717
+ inicial,
1718
+ extra
1719
+ }) {
1720
+ const base = useId();
1721
+ const [activa, setActiva] = useState(inicial ?? pestanas[0]?.id);
1722
+ const ids = pestanas.map((p) => p.id);
1723
+ const teclado = (e) => {
1724
+ if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
1725
+ e.preventDefault();
1726
+ const i = ids.indexOf(activa ?? "");
1727
+ const siguiente = ids[(i + (e.key === "ArrowRight" ? 1 : ids.length - 1)) % ids.length];
1728
+ setActiva(siguiente);
1729
+ document.getElementById(`${base}-pestana-${siguiente}`)?.focus();
1730
+ };
1731
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1732
+ /* @__PURE__ */ jsxs("div", { role: "tablist", "aria-label": etiqueta, className: "inline-flex gap-1 rounded-lg bg-[var(--tema-badge-neutro)] p-1 print:hidden", children: [
1733
+ pestanas.map((p) => /* @__PURE__ */ jsx(
1734
+ "button",
1735
+ {
1736
+ type: "button",
1737
+ role: "tab",
1738
+ id: `${base}-pestana-${p.id}`,
1739
+ "aria-selected": activa === p.id,
1740
+ "aria-controls": `${base}-panel-${p.id}`,
1741
+ tabIndex: activa === p.id ? 0 : -1,
1742
+ onClick: () => setActiva(p.id),
1743
+ onKeyDown: teclado,
1744
+ className: `rounded-md px-3 py-1 text-xs font-medium ${activa === p.id ? "bg-[var(--tema-fondo)] text-[var(--tema-primario-oscuro)] shadow-sm" : "text-[#565656] hover:text-[var(--tema-primario)]"}`,
1745
+ children: p.texto
1746
+ },
1747
+ p.id
1748
+ )),
1749
+ extra && /* @__PURE__ */ jsx("span", { className: "self-center pr-1", children: extra })
1750
+ ] }),
1751
+ pestanas.map((p) => /* @__PURE__ */ jsx(
1752
+ "div",
1753
+ {
1754
+ role: "tabpanel",
1755
+ id: `${base}-panel-${p.id}`,
1756
+ "aria-labelledby": `${base}-pestana-${p.id}`,
1757
+ className: `space-y-3 ${activa !== p.id ? "hidden print:block" : ""}`,
1758
+ children: p.contenido
1759
+ },
1760
+ p.id
1761
+ ))
1762
+ ] });
1763
+ }
1764
+ function PantallaAcceso({
1765
+ producto,
1766
+ titulo,
1767
+ descripcion,
1768
+ hrefLogin,
1769
+ etiquetaBoton = "Iniciar sesi\xF3n con Microsoft 365",
1770
+ capacidades = [],
1771
+ logo,
1772
+ error
1773
+ }) {
1774
+ return /* @__PURE__ */ jsxs("main", { className: "reflejo-fondo flex min-h-screen flex-1 flex-col items-center justify-center gap-10 bg-[#f4f8fc] px-6 py-16", children: [
1775
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center gap-2", children: [
1776
+ logo,
1777
+ logo && /* @__PURE__ */ jsx("span", { className: "h-6 w-px bg-[var(--tema-borde)]" }),
1778
+ /* @__PURE__ */ jsx("span", { className: "text-base font-semibold text-[var(--tema-primario)]", children: producto })
1779
+ ] }),
1780
+ /* @__PURE__ */ jsxs("div", { className: "flex max-w-xl flex-col items-center gap-4 text-center", children: [
1781
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-[var(--tema-primario-oscuro)] sm:text-4xl", children: titulo }),
1782
+ /* @__PURE__ */ jsx("p", { className: "text-base text-[#565656]", children: descripcion }),
1783
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "rounded-md bg-[var(--tema-badge-error)] px-3 py-2 text-sm text-[var(--tema-peligro)]", children: error }),
1784
+ /* @__PURE__ */ jsxs(
1785
+ "a",
1786
+ {
1787
+ href: hrefLogin,
1788
+ className: "mt-2 inline-flex items-center gap-2 rounded-md bg-[var(--tema-texto)] px-5 py-2.5 text-sm font-medium text-white transition hover:brightness-125",
1789
+ children: [
1790
+ /* @__PURE__ */ jsx(ICONO_LOGIN_MICROSOFT, { size: TAMANO_BOTON, "aria-hidden": "true" }),
1791
+ etiquetaBoton
1792
+ ]
572
1793
  }
573
- });
1794
+ )
1795
+ ] }),
1796
+ capacidades.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid w-full max-w-4xl gap-4 sm:grid-cols-3", children: capacidades.map(({ icono: Icono, titulo: t, descripcion: d }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-xl border border-[var(--tema-borde)] bg-white p-5 text-left shadow-sm", children: [
1797
+ /* @__PURE__ */ jsx("span", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--tema-badge-neutro)] text-[var(--tema-primario)]", children: /* @__PURE__ */ jsx(Icono, { size: TAMANO_BOTON, "aria-hidden": "true" }) }),
1798
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-[var(--tema-texto)]", children: t }),
1799
+ /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-[#565656]", children: d })
1800
+ ] }, t)) })
1801
+ ] });
1802
+ }
1803
+ function PantallaCargando({ etiqueta = "Cargando..." }) {
1804
+ return /* @__PURE__ */ jsx("div", { className: "flex min-h-[60vh] w-full flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(SpinnerMarca, { tamano: 140, etiqueta }) });
1805
+ }
1806
+ function FechaHoraLocal({ iso }) {
1807
+ const [texto, setTexto] = useState(() => _isoLegible(iso));
1808
+ useEffect(() => {
1809
+ setTexto(formatearFechaHora(iso));
1810
+ }, [iso]);
1811
+ return /* @__PURE__ */ jsx("span", { suppressHydrationWarning: true, children: texto });
1812
+ }
1813
+ function _isoLegible(iso) {
1814
+ if (!iso) return "\u2014";
1815
+ const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/);
1816
+ if (!m) return iso;
1817
+ const [, anio, mes, dia, hora, min, seg] = m;
1818
+ return `${anio}/${mes}/${dia} ${hora}:${min}:${seg}`;
1819
+ }
1820
+ var ANCHOS = {
1821
+ completo: "",
1822
+ "2xl": "mx-auto max-w-2xl",
1823
+ "3xl": "mx-auto max-w-3xl",
1824
+ "4xl": "mx-auto max-w-4xl"
1825
+ };
1826
+ function PaginaContenido({
1827
+ titulo,
1828
+ icono,
1829
+ descripcion,
1830
+ acciones,
1831
+ ancho = "completo",
1832
+ children
1833
+ }) {
1834
+ return /* @__PURE__ */ jsxs("main", { className: `w-full flex-1 px-6 py-10 ${ANCHOS[ancho]}`, children: [
1835
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
1836
+ /* @__PURE__ */ jsxs("h1", { className: "flex items-center gap-2 text-xl font-semibold text-[var(--tema-primario-oscuro)]", children: [
1837
+ icono,
1838
+ titulo
1839
+ ] }),
1840
+ acciones && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: acciones })
1841
+ ] }),
1842
+ descripcion && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-[#807f7f]", children: descripcion }),
1843
+ /* @__PURE__ */ jsx("div", { className: "mt-6", children })
1844
+ ] });
1845
+ }
1846
+ function usePaginacionLocal(items, tamanoInicial = 10) {
1847
+ const [tamano, setTamano] = useState(tamanoInicial);
1848
+ const [paginaActual, setPaginaActual] = useState(1);
1849
+ const totalPaginas = Math.max(1, Math.ceil(items.length / tamano));
1850
+ const pagina = Math.min(paginaActual, totalPaginas);
1851
+ const itemsPagina = useMemo(() => {
1852
+ const inicio = (pagina - 1) * tamano;
1853
+ return items.slice(inicio, inicio + tamano);
1854
+ }, [items, pagina, tamano]);
1855
+ return {
1856
+ itemsPagina,
1857
+ paginaActual: pagina,
1858
+ totalPaginas,
1859
+ tamano,
1860
+ totalItems: items.length,
1861
+ siguiente: () => setPaginaActual((p) => Math.min(p + 1, totalPaginas)),
1862
+ anterior: () => setPaginaActual((p) => Math.max(p - 1, 1)),
1863
+ cambiarTamano: (n) => {
1864
+ setTamano(n);
1865
+ setPaginaActual(1);
574
1866
  }
575
- return trigger;
576
- }, [trigger, handleOpen]);
577
- return /* @__PURE__ */ jsxs(Fragment, { children: [
578
- triggerNode,
579
- /* @__PURE__ */ jsx(Modal, { open: isOpen, onOk: handleOk, onCancel: handleCancel, ...modalProps, children })
1867
+ };
1868
+ }
1869
+ var TAMANOS = [10, 25, 50, 100];
1870
+ function PaginadorLocal({
1871
+ paginaActual,
1872
+ totalPaginas,
1873
+ totalItems,
1874
+ tamano,
1875
+ tamanos = TAMANOS,
1876
+ onAnterior,
1877
+ onSiguiente,
1878
+ onCambiarTamano
1879
+ }) {
1880
+ if (totalPaginas <= 1) return null;
1881
+ const desde = totalItems === 0 ? 0 : (paginaActual - 1) * tamano + 1;
1882
+ const hasta = Math.min(paginaActual * tamano, totalItems);
1883
+ return /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-between gap-3", children: [
1884
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1885
+ onCambiarTamano && /* @__PURE__ */ jsxs(Fragment, { children: [
1886
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-[#807f7f]", children: "Por p\xE1gina" }),
1887
+ /* @__PURE__ */ jsx(
1888
+ "select",
1889
+ {
1890
+ value: tamano,
1891
+ onChange: (e) => onCambiarTamano(Number(e.target.value)),
1892
+ "aria-label": "Filas por p\xE1gina",
1893
+ className: "rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] px-2 py-1.5 text-sm focus:border-[var(--tema-primario)] focus:outline-none",
1894
+ children: tamanos.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
1895
+ }
1896
+ )
1897
+ ] }),
1898
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-[#807f7f]", children: [
1899
+ desde,
1900
+ "\u2013",
1901
+ hasta,
1902
+ " de ",
1903
+ totalItems
1904
+ ] })
1905
+ ] }),
1906
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1907
+ /* @__PURE__ */ jsxs(
1908
+ "button",
1909
+ {
1910
+ type: "button",
1911
+ onClick: onAnterior,
1912
+ disabled: paginaActual <= 1,
1913
+ "aria-label": "P\xE1gina anterior",
1914
+ className: "inline-flex items-center gap-1 rounded-md border border-[var(--tema-borde)] px-3 py-1.5 text-sm font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50",
1915
+ children: [
1916
+ /* @__PURE__ */ jsx(ICONO_PAGINA_ANTERIOR, { size: TAMANO_BADGE, "aria-hidden": "true" }),
1917
+ "Anterior"
1918
+ ]
1919
+ }
1920
+ ),
1921
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-[#807f7f]", children: [
1922
+ paginaActual,
1923
+ " / ",
1924
+ totalPaginas
1925
+ ] }),
1926
+ /* @__PURE__ */ jsxs(
1927
+ "button",
1928
+ {
1929
+ type: "button",
1930
+ onClick: onSiguiente,
1931
+ disabled: paginaActual >= totalPaginas,
1932
+ "aria-label": "P\xE1gina siguiente",
1933
+ className: "inline-flex items-center gap-1 rounded-md border border-[var(--tema-borde)] px-3 py-1.5 text-sm font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50",
1934
+ children: [
1935
+ "Siguiente",
1936
+ /* @__PURE__ */ jsx(ICONO_PAGINA_SIGUIENTE, { size: TAMANO_BADGE, "aria-hidden": "true" })
1937
+ ]
1938
+ }
1939
+ )
1940
+ ] })
1941
+ ] });
1942
+ }
1943
+ function PaginadorUrl({
1944
+ pagina,
1945
+ totalPaginas,
1946
+ totalItems,
1947
+ tamano
1948
+ }) {
1949
+ const { ir, pendiente } = useNavegarListado();
1950
+ if (totalPaginas <= 1) return null;
1951
+ return /* @__PURE__ */ jsxs("div", { className: "relative", "aria-busy": pendiente || void 0, children: [
1952
+ /* @__PURE__ */ jsx(
1953
+ PaginadorLocal,
1954
+ {
1955
+ paginaActual: pagina,
1956
+ totalPaginas,
1957
+ totalItems,
1958
+ tamano,
1959
+ tamanos: TAMANOS_LISTADO,
1960
+ onAnterior: () => ir({ pagina: String(Math.max(1, pagina - 1)) }, { scroll: true }),
1961
+ onSiguiente: () => ir({ pagina: String(Math.min(totalPaginas, pagina + 1)) }, { scroll: true })
1962
+ }
1963
+ ),
1964
+ pendiente && /* @__PURE__ */ jsxs("span", { role: "status", className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-[var(--tema-primario)]", children: [
1965
+ /* @__PURE__ */ jsx(ICONO_CARGANDO, { size: 14, className: "animate-spin motion-reduce:animate-none", "aria-hidden": "true" }),
1966
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Cargando p\xE1gina..." })
1967
+ ] })
580
1968
  ] });
581
1969
  }
582
- var normalize = (value) => (value ?? "").toString().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
583
- var extractText = (node) => {
584
- if (node === null || node === void 0) return "";
585
- if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
586
- return String(node);
1970
+ var DEBOUNCE_MS = 300;
1971
+ var CAMPO = "rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] px-2 py-1.5 text-sm text-[#3c3c3b] focus:border-[var(--tema-primario)] focus:outline-none";
1972
+ function BarraListado({
1973
+ filtros,
1974
+ totalFiltrado,
1975
+ total,
1976
+ placeholder = "Buscar...",
1977
+ etiquetaBusqueda = "Buscar",
1978
+ extra
1979
+ }) {
1980
+ const { ir, pendiente, params } = useNavegarListado();
1981
+ const qUrl = params.get("q") ?? "";
1982
+ const [texto, setTexto] = useState(qUrl);
1983
+ const [numeros, setNumeros] = useState(
1984
+ () => Object.fromEntries(filtros.filter((f) => f.tipo === "numero").map((f) => [f.clave, params.get(f.clave) ?? ""]))
1985
+ );
1986
+ const idBuscar = useId();
1987
+ const temporizador = useRef(null);
1988
+ useEffect(() => setTexto(qUrl), [qUrl]);
1989
+ useEffect(() => () => cancelar(), []);
1990
+ function cancelar() {
1991
+ if (temporizador.current) clearTimeout(temporizador.current);
1992
+ temporizador.current = null;
587
1993
  }
588
- if (Array.isArray(node)) return node.map(extractText).join(" ");
589
- if (typeof node === "object" && "props" in node) {
590
- const props = node.props;
591
- return extractText(props?.children);
1994
+ function aplicarBusqueda(valor) {
1995
+ cancelar();
1996
+ if (valor.trim() === qUrl.trim()) return;
1997
+ ir({ q: valor.trim() || null });
592
1998
  }
593
- return "";
594
- };
595
- function FilterSelect({
596
- options,
597
- showSearch = false,
598
- filterOption,
599
- className,
600
- style,
601
- ...rest
602
- }) {
603
- const defaultFilter = (input, option) => {
604
- if (!option) return false;
605
- const query = normalize(input);
606
- const label = normalize(extractText(option.label));
607
- const value = normalize(typeof option.value === "string" ? option.value : "");
608
- return label.includes(query) || value.includes(query);
609
- };
610
- const resolvedFilter = showSearch && !filterOption ? defaultFilter : filterOption;
611
- return /* @__PURE__ */ jsx(
612
- Select,
1999
+ function alEscribir(valor) {
2000
+ setTexto(valor);
2001
+ cancelar();
2002
+ temporizador.current = setTimeout(() => aplicarBusqueda(valor), DEBOUNCE_MS);
2003
+ }
2004
+ function limpiar() {
2005
+ setTexto("");
2006
+ aplicarBusqueda("");
2007
+ }
2008
+ function aplicarNumero(clave) {
2009
+ const valor = (numeros[clave] ?? "").trim();
2010
+ if (valor === (params.get(clave) ?? "")) return;
2011
+ ir({ [clave]: valor || null });
2012
+ }
2013
+ function alEnviar(e) {
2014
+ e.preventDefault();
2015
+ e.stopPropagation();
2016
+ cancelar();
2017
+ const cambios = {};
2018
+ if (texto.trim() !== qUrl.trim()) cambios.q = texto.trim() || null;
2019
+ for (const [clave, valor] of Object.entries(numeros)) {
2020
+ if (valor.trim() !== (params.get(clave) ?? "")) cambios[clave] = valor.trim() || null;
2021
+ }
2022
+ if (Object.keys(cambios).length > 0) ir(cambios);
2023
+ }
2024
+ const tamano = params.get("tamano") ?? String(TAMANO_LISTADO_DEFAULT);
2025
+ return /* @__PURE__ */ jsxs(
2026
+ "form",
613
2027
  {
614
- options,
615
- showSearch,
616
- filterOption: resolvedFilter,
617
- optionFilterProp: "label",
618
- className,
619
- style: { width: "100%", ...style },
620
- ...rest
2028
+ role: "search",
2029
+ onSubmit: alEnviar,
2030
+ className: "flex flex-wrap items-end gap-x-3 gap-y-2 rounded-lg border border-[var(--tema-borde)] bg-[#f9fafb] px-3 py-2",
2031
+ children: [
2032
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-[16rem] flex-1 flex-col gap-1", children: [
2033
+ /* @__PURE__ */ jsx("label", { htmlFor: idBuscar, className: "text-xs font-medium text-[#565656]", children: etiquetaBusqueda }),
2034
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2035
+ /* @__PURE__ */ jsx(
2036
+ ICONO_BUSCAR,
2037
+ {
2038
+ size: 14,
2039
+ "aria-hidden": "true",
2040
+ className: "pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-[#807f7f]"
2041
+ }
2042
+ ),
2043
+ /* @__PURE__ */ jsx(
2044
+ "input",
2045
+ {
2046
+ id: idBuscar,
2047
+ type: "search",
2048
+ value: texto,
2049
+ onChange: (e) => alEscribir(e.target.value),
2050
+ onKeyDown: (e) => {
2051
+ if (e.key === "Escape" && texto) {
2052
+ e.preventDefault();
2053
+ limpiar();
2054
+ }
2055
+ },
2056
+ placeholder,
2057
+ autoComplete: "off",
2058
+ spellCheck: false,
2059
+ className: `${CAMPO} w-full pl-7 pr-8 [&::-webkit-search-cancel-button]:hidden`
2060
+ }
2061
+ ),
2062
+ texto && /* @__PURE__ */ jsx(
2063
+ "button",
2064
+ {
2065
+ type: "button",
2066
+ onClick: limpiar,
2067
+ "aria-label": "Limpiar b\xFAsqueda",
2068
+ title: "Limpiar b\xFAsqueda",
2069
+ className: "absolute right-1 top-1/2 -translate-y-1/2 rounded p-1 text-[#807f7f] transition hover:bg-[var(--tema-badge-neutro)]",
2070
+ children: /* @__PURE__ */ jsx(ICONO_LIMPIAR, { size: 12, "aria-hidden": "true" })
2071
+ }
2072
+ )
2073
+ ] })
2074
+ ] }),
2075
+ filtros.length > 0 && /* @__PURE__ */ jsx(ICONO_FILTRO, { size: TAMANO_BADGE, "aria-hidden": "true", className: "mb-2 hidden text-[#807f7f] sm:block" }),
2076
+ filtros.map((f) => {
2077
+ const id = `${idBuscar}-${f.clave}`;
2078
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2079
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-[#565656]", children: f.etiqueta }),
2080
+ f.tipo === "numero" ? /* @__PURE__ */ jsx(
2081
+ "input",
2082
+ {
2083
+ id,
2084
+ type: "number",
2085
+ inputMode: "numeric",
2086
+ min: f.min,
2087
+ max: f.max,
2088
+ value: numeros[f.clave] ?? "",
2089
+ onChange: (e) => setNumeros((n) => ({ ...n, [f.clave]: e.target.value })),
2090
+ onBlur: () => aplicarNumero(f.clave),
2091
+ className: `${CAMPO} w-24`
2092
+ }
2093
+ ) : /* @__PURE__ */ jsx(
2094
+ "select",
2095
+ {
2096
+ id,
2097
+ value: params.get(f.clave) ?? "",
2098
+ onChange: (e) => ir({ [f.clave]: e.target.value || null }),
2099
+ className: CAMPO,
2100
+ children: f.opciones.map((o) => /* @__PURE__ */ jsx("option", { value: o.valor, children: o.etiqueta }, o.valor))
2101
+ }
2102
+ )
2103
+ ] }, f.clave);
2104
+ }),
2105
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2106
+ /* @__PURE__ */ jsx("label", { htmlFor: `${idBuscar}-tamano`, className: "text-xs font-medium text-[#565656]", children: "Por p\xE1gina" }),
2107
+ /* @__PURE__ */ jsx(
2108
+ "select",
2109
+ {
2110
+ id: `${idBuscar}-tamano`,
2111
+ "aria-label": "Filas por p\xE1gina",
2112
+ value: tamano,
2113
+ onChange: (e) => ir({ tamano: e.target.value === String(TAMANO_LISTADO_DEFAULT) ? null : e.target.value }),
2114
+ className: CAMPO,
2115
+ children: TAMANOS_LISTADO.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
2116
+ }
2117
+ )
2118
+ ] }),
2119
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-3 pb-1.5", children: [
2120
+ pendiente ? /* @__PURE__ */ jsxs("span", { role: "status", className: "inline-flex items-center gap-1 text-xs text-[#807f7f]", children: [
2121
+ /* @__PURE__ */ jsx(ICONO_CARGANDO, { size: 12, "aria-hidden": "true", className: "animate-spin text-[var(--tema-primario)] motion-reduce:animate-none" }),
2122
+ "Actualizando..."
2123
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-[#807f7f]", "aria-live": "polite", children: [
2124
+ totalFiltrado === total ? `${total} en total` : `${totalFiltrado} de ${total}`,
2125
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: "plataforma.listado_resultados" })
2126
+ ] }),
2127
+ extra
2128
+ ] })
2129
+ ]
621
2130
  }
622
2131
  );
623
2132
  }
624
- var resolveMessage = (template, fileName, fallback) => {
625
- if (!template) return fallback.replace("{file}", fileName);
626
- return typeof template === "function" ? template(fileName) : template.replace("{file}", fileName);
627
- };
628
- function FileUploadDragger({
629
- successMessage,
630
- errorMessage,
631
- showStatusMessages = true,
632
- hintText = "Click or drag a file to this area to upload",
633
- onUploadSuccess,
634
- onUploadError,
635
- onChange: externalOnChange,
636
- children,
637
- ...uploadProps
638
- }) {
639
- const message = useAmsMessage();
640
- const mergedProps = useMemo(
641
- () => ({
642
- ...uploadProps,
643
- onChange(info) {
644
- const { status, name: fileName } = info.file;
645
- if (showStatusMessages) {
646
- if (status === "done") {
647
- message.success(resolveMessage(successMessage, fileName, `${fileName} uploaded successfully.`));
648
- onUploadSuccess?.(info);
649
- } else if (status === "error") {
650
- message.error(resolveMessage(errorMessage, fileName, `${fileName} failed to upload.`));
651
- onUploadError?.(info);
2133
+ function EncabezadoOrdenUrl({
2134
+ clave,
2135
+ etiqueta,
2136
+ icono,
2137
+ info,
2138
+ ordenActual,
2139
+ dirActual,
2140
+ alineado = "left",
2141
+ celda
2142
+ }) {
2143
+ const { ir } = useNavegarListado();
2144
+ const activa = ordenActual === clave;
2145
+ const dir = activa ? dirActual : null;
2146
+ const Flecha = dir === "asc" ? ICONO_ORDENAR_ASC : dir === "desc" ? ICONO_ORDENAR_DESC : ICONO_ORDENAR_NEUTRO;
2147
+ const descripcion = dir === "asc" ? "ascendente" : dir === "desc" ? "descendente" : "sin ordenar";
2148
+ function alternar() {
2149
+ if (!activa) return ir({ orden: clave, dir: "asc" });
2150
+ if (dir === "asc") return ir({ orden: clave, dir: "desc" });
2151
+ return ir({ orden: null, dir: null });
2152
+ }
2153
+ return /* @__PURE__ */ jsx(
2154
+ "th",
2155
+ {
2156
+ ...celda,
2157
+ scope: "col",
2158
+ "aria-sort": dir === "asc" ? "ascending" : dir === "desc" ? "descending" : "none",
2159
+ className: `whitespace-nowrap px-3 py-2 font-medium ${alineado === "right" ? "text-right" : ""}`,
2160
+ children: /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 ${alineado === "right" ? "justify-end" : ""}`, children: [
2161
+ /* @__PURE__ */ jsxs(
2162
+ "button",
2163
+ {
2164
+ type: "button",
2165
+ onClick: alternar,
2166
+ title: `Ordenar por ${etiqueta}`,
2167
+ "aria-label": `Ordenar por ${etiqueta} (orden actual: ${descripcion})`,
2168
+ className: `inline-flex items-center gap-1 transition hover:text-[var(--tema-primario)] ${activa ? "text-[var(--tema-primario)]" : ""}`,
2169
+ children: [
2170
+ icono,
2171
+ etiqueta,
2172
+ /* @__PURE__ */ jsx(Flecha, { size: 12, "aria-hidden": "true", className: activa ? "" : "opacity-40" })
2173
+ ]
652
2174
  }
653
- }
654
- externalOnChange?.(info);
655
- }
656
- }),
657
- [uploadProps, showStatusMessages, message, successMessage, errorMessage, onUploadSuccess, onUploadError, externalOnChange]
2175
+ ),
2176
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
2177
+ ] })
2178
+ }
658
2179
  );
659
- return /* @__PURE__ */ jsx(Upload.Dragger, { ...mergedProps, children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
660
- /* @__PURE__ */ jsx("p", { className: "ant-upload-drag-icon", children: /* @__PURE__ */ jsx(InboxOutlined, {}) }),
661
- /* @__PURE__ */ jsx("p", { className: "ant-upload-text", children: hintText })
662
- ] }) });
663
2180
  }
664
- function PageHeader({
665
- title,
666
- breadcrumbs,
667
- separator = ">",
668
- uppercaseTitle = true,
669
- extra,
670
- className
2181
+ function fija(lado, idx = 0, opciones) {
2182
+ if (lado === "der") return { "data-fija": "der" };
2183
+ const primaria = opciones?.primaria ?? idx === 0;
2184
+ return primaria ? { "data-fija": "izq", "data-fija-idx": idx, "data-fija-primaria": "" } : { "data-fija": "izq", "data-fija-idx": idx };
2185
+ }
2186
+ function col(id) {
2187
+ return { "data-col": id };
2188
+ }
2189
+ function CeldaIdentidad({ texto, className = "" }) {
2190
+ const valor = texto ?? "";
2191
+ return /* @__PURE__ */ jsx("span", { title: valor, className: `block max-w-[16rem] truncate ${className}`, children: valor });
2192
+ }
2193
+ var CLASE_ACCION_FILA = "inline-flex items-center gap-1 whitespace-nowrap rounded-md border border-[var(--tema-borde)] px-2.5 py-1 text-xs hover:bg-[var(--tema-badge-neutro)] disabled:opacity-50";
2194
+ var ANCHO_BASE = 160;
2195
+ function TablaConfigurable({
2196
+ columnas,
2197
+ filas,
2198
+ claveFila,
2199
+ clavePrefs,
2200
+ columnaFija,
2201
+ columnaAcciones,
2202
+ idsFijos = [],
2203
+ etiqueta = "Resultados",
2204
+ mensajeVacio = "Sin resultados.",
2205
+ ajuste,
2206
+ pie,
2207
+ barraExtra
671
2208
  }) {
672
- return /* @__PURE__ */ jsxs("div", { className, style: { marginBottom: 32 }, children: [
2209
+ const fijos = idsFijos.filter((id) => columnas.some((c) => c.id === id));
2210
+ const idsDefault = columnas.map((c) => c.id).filter((id) => !fijos.includes(id));
2211
+ const { idsVisibles, idsOrdenados, ocultas, anchoDe, mover, redimensionar, alternarVisible, restablecer } = useTablaPreferencias(clavePrefs, idsDefault);
2212
+ const porId = new Map(columnas.map((c) => [c.id, c]));
2213
+ const [menuAbierto, setMenuAbierto] = useState(false);
2214
+ const [orden, setOrden] = useState(null);
2215
+ const arrastrando = useRef(null);
2216
+ const filasOrdenadas = (() => {
2217
+ if (!orden) return filas;
2218
+ const col2 = porId.get(orden.id);
2219
+ if (!col2?.valorOrden) return filas;
2220
+ const factor = orden.dir === "asc" ? 1 : -1;
2221
+ return [...filas].sort((a, b) => {
2222
+ const va = col2.valorOrden(a);
2223
+ const vb = col2.valorOrden(b);
2224
+ if (va === void 0 || va === null || va === "") return 1;
2225
+ if (vb === void 0 || vb === null || vb === "") return -1;
2226
+ if (typeof va === "number" && typeof vb === "number") {
2227
+ return (va - vb) * factor;
2228
+ }
2229
+ return String(va).localeCompare(String(vb), "es") * factor;
2230
+ });
2231
+ })();
2232
+ function alternarOrdenColumna(id) {
2233
+ setOrden((prev) => {
2234
+ if (prev?.id !== id) return { id, dir: "asc" };
2235
+ if (prev.dir === "asc") return { id, dir: "desc" };
2236
+ return null;
2237
+ });
2238
+ }
2239
+ const colSpanTotal = fijos.length + idsVisibles.length + (columnaFija ? 1 : 0) + (columnaAcciones ? 1 : 0);
2240
+ const baseFijos = columnaFija ? 1 : 0;
2241
+ const atributosFijo = (i) => fija("izq", baseFijos + i, { primaria: i === 0 });
2242
+ const atributosCasilla = fija("izq", 0, { primaria: fijos.length === 0 });
2243
+ const anchoEfectivo = (id) => {
2244
+ const persistido = anchoDe(id);
2245
+ if (persistido !== void 0) return persistido;
2246
+ return porId.get(id)?.anchoDefault ?? ANCHO_BASE;
2247
+ };
2248
+ const ANCHO_FIJA = 44;
2249
+ const ANCHO_ACCIONES = 96;
2250
+ const anchoTabla = (columnaFija ? ANCHO_FIJA : 0) + (columnaAcciones ? ANCHO_ACCIONES : 0) + [...fijos, ...idsVisibles].reduce((suma, id) => suma + anchoEfectivo(id), 0);
2251
+ const contenidoEncabezado = (col2) => /* @__PURE__ */ jsxs(Fragment, { children: [
2252
+ col2.ordenable ? /* @__PURE__ */ jsx(
2253
+ EncabezadoOrdenable,
2254
+ {
2255
+ columnaId: col2.id,
2256
+ contenido: col2.encabezado,
2257
+ etiqueta: col2.etiquetaOrden ?? (typeof col2.encabezado === "string" ? col2.encabezado : "esta columna"),
2258
+ orden,
2259
+ onAlternar: () => alternarOrdenColumna(col2.id)
2260
+ }
2261
+ ) : col2.encabezado,
2262
+ col2.info && /* @__PURE__ */ jsx(InfoExplicacion, { id: col2.info })
2263
+ ] });
2264
+ const barra = /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-2", children: [
673
2265
  /* @__PURE__ */ jsxs(
674
- "div",
2266
+ "button",
675
2267
  {
676
- style: {
677
- display: "flex",
678
- alignItems: "center",
679
- justifyContent: "space-between",
680
- gap: 16
681
- },
2268
+ type: "button",
2269
+ onClick: () => setMenuAbierto((v) => !v),
2270
+ title: "Mostrar u ocultar columnas",
2271
+ "aria-label": "Mostrar u ocultar columnas",
2272
+ className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-borde)] px-2.5 py-1.5 text-xs font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)]",
682
2273
  children: [
683
- /* @__PURE__ */ jsx(
684
- "h1",
685
- {
686
- style: {
687
- fontSize: 20,
688
- fontWeight: 700,
689
- margin: 0,
690
- textTransform: uppercaseTitle ? "uppercase" : void 0
691
- },
692
- children: title
693
- }
694
- ),
695
- extra ? /* @__PURE__ */ jsx("div", { children: extra }) : null
2274
+ /* @__PURE__ */ jsx(ICONO_COLUMNAS, { size: TAMANO_BADGE, "aria-hidden": "true" }),
2275
+ "Columnas"
696
2276
  ]
697
2277
  }
698
2278
  ),
699
- breadcrumbs && breadcrumbs.length > 0 ? /* @__PURE__ */ jsx(
700
- Breadcrumb,
2279
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: "plataforma.tabla_columnas" }),
2280
+ /* @__PURE__ */ jsxs(
2281
+ "button",
701
2282
  {
702
- separator,
703
- items: breadcrumbs,
704
- style: { fontSize: 13, marginTop: 8 }
2283
+ type: "button",
2284
+ onClick: restablecer,
2285
+ title: "Restablecer orden y tama\xF1o de columnas",
2286
+ "aria-label": "Restablecer columnas",
2287
+ className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--tema-borde)] px-2.5 py-1.5 text-xs font-medium text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)]",
2288
+ children: [
2289
+ /* @__PURE__ */ jsx(ICONO_RESTABLECER, { size: TAMANO_BADGE, "aria-hidden": "true" }),
2290
+ "Restablecer"
2291
+ ]
705
2292
  }
706
- ) : null
2293
+ ),
2294
+ menuAbierto && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-full z-30 mt-1 w-56 rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] p-2 shadow-lg", children: [
2295
+ /* @__PURE__ */ jsx("p", { className: "flex items-center gap-1 px-1 pb-1 text-[10px] font-semibold uppercase tracking-wide text-[#9a9a9a]", children: "Columnas visibles" }),
2296
+ idsOrdenados.map((id) => {
2297
+ const col2 = porId.get(id);
2298
+ if (!col2) return null;
2299
+ const visible = !ocultas.has(id);
2300
+ const Icono = visible ? ICONO_COLUMNA_VISIBLE : ICONO_COLUMNA_OCULTA;
2301
+ return /* @__PURE__ */ jsxs(
2302
+ "button",
2303
+ {
2304
+ type: "button",
2305
+ onClick: () => alternarVisible(id),
2306
+ className: "flex w-full items-center gap-2 rounded px-1.5 py-1 text-left text-sm text-[#3c3c3b] transition hover:bg-[var(--tema-badge-neutro)]",
2307
+ children: [
2308
+ /* @__PURE__ */ jsx(Icono, { size: 14, "aria-hidden": "true", className: visible ? "" : "opacity-40" }),
2309
+ /* @__PURE__ */ jsx("span", { className: visible ? "" : "opacity-50", children: col2.encabezadoMenu ?? col2.encabezado })
2310
+ ]
2311
+ },
2312
+ id
2313
+ );
2314
+ })
2315
+ ] }),
2316
+ barraExtra
707
2317
  ] });
2318
+ return /* @__PURE__ */ jsx(MarcoTabla, { clave: clavePrefs, etiqueta, barra, ajuste, pie, children: /* @__PURE__ */ jsxs(
2319
+ "table",
2320
+ {
2321
+ className: "table-fixed divide-y divide-[var(--tema-borde)] text-sm",
2322
+ style: { width: anchoTabla, minWidth: "100%" },
2323
+ children: [
2324
+ /* @__PURE__ */ jsxs("colgroup", { children: [
2325
+ columnaFija && /* @__PURE__ */ jsx("col", { style: { width: ANCHO_FIJA } }),
2326
+ fijos.map((id) => /* @__PURE__ */ jsx("col", { style: { width: anchoEfectivo(id) } }, id)),
2327
+ idsVisibles.map((id) => /* @__PURE__ */ jsx("col", { style: { width: anchoEfectivo(id) } }, id)),
2328
+ columnaAcciones && /* @__PURE__ */ jsx("col", { style: { width: ANCHO_ACCIONES } })
2329
+ ] }),
2330
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs font-medium text-[#807f7f]", children: [
2331
+ columnaFija && /* @__PURE__ */ jsx("th", { className: "px-3 py-2", ...atributosCasilla, children: columnaFija.encabezado ?? null }),
2332
+ fijos.map((id, i) => {
2333
+ const col2 = porId.get(id);
2334
+ return /* @__PURE__ */ jsxs("th", { className: "relative overflow-hidden px-3 py-2", ...atributosFijo(i), children: [
2335
+ /* @__PURE__ */ jsx("span", { className: `flex items-center gap-1 truncate ${col2.alineado === "right" ? "justify-end" : ""}`, children: contenidoEncabezado(col2) }),
2336
+ /* @__PURE__ */ jsx(
2337
+ ResizerColumna,
2338
+ {
2339
+ anchoActual: anchoEfectivo(id),
2340
+ onFinRedimension: (nuevoAncho) => redimensionar(id, nuevoAncho)
2341
+ }
2342
+ )
2343
+ ] }, id);
2344
+ }),
2345
+ idsVisibles.map((id) => {
2346
+ const col2 = porId.get(id);
2347
+ if (!col2) return null;
2348
+ const idxOrden = idsOrdenados.indexOf(id);
2349
+ return /* @__PURE__ */ jsxs(
2350
+ "th",
2351
+ {
2352
+ className: "relative overflow-hidden px-3 py-2",
2353
+ onDragOver: (e) => e.preventDefault(),
2354
+ onDrop: () => {
2355
+ if (arrastrando.current !== null && arrastrando.current !== idxOrden) {
2356
+ mover(arrastrando.current, idxOrden);
2357
+ }
2358
+ arrastrando.current = null;
2359
+ },
2360
+ children: [
2361
+ /* @__PURE__ */ jsxs(
2362
+ "span",
2363
+ {
2364
+ className: `flex items-center gap-1 truncate ${col2.alineado === "right" ? "justify-end" : ""}`,
2365
+ children: [
2366
+ /* @__PURE__ */ jsx(
2367
+ "span",
2368
+ {
2369
+ draggable: true,
2370
+ onDragStart: () => {
2371
+ arrastrando.current = idxOrden;
2372
+ },
2373
+ onDragEnd: () => {
2374
+ arrastrando.current = null;
2375
+ },
2376
+ "aria-label": "Arrastrar para reordenar la columna",
2377
+ className: "shrink-0 cursor-grab text-[#c4c4c4] hover:text-[var(--tema-primario)]",
2378
+ children: /* @__PURE__ */ jsx(ICONO_ARRASTRAR_COLUMNA, { size: 12, "aria-hidden": "true" })
2379
+ }
2380
+ ),
2381
+ contenidoEncabezado(col2)
2382
+ ]
2383
+ }
2384
+ ),
2385
+ /* @__PURE__ */ jsx(
2386
+ ResizerColumna,
2387
+ {
2388
+ anchoActual: anchoEfectivo(id),
2389
+ onFinRedimension: (nuevoAncho) => redimensionar(id, nuevoAncho)
2390
+ }
2391
+ )
2392
+ ]
2393
+ },
2394
+ id
2395
+ );
2396
+ }),
2397
+ columnaAcciones && /* @__PURE__ */ jsx("th", { className: "px-3 py-2", ...fija("der"), children: columnaAcciones.encabezado ?? null })
2398
+ ] }) }),
2399
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-[var(--tema-borde)]", children: filasOrdenadas.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: colSpanTotal, className: "px-3 py-6 text-center text-sm text-[#807f7f]", children: mensajeVacio }) }) : filasOrdenadas.map((fila) => /* @__PURE__ */ jsxs("tr", { className: "text-[#3c3c3b]", children: [
2400
+ columnaFija && /* @__PURE__ */ jsx("td", { className: "px-3 py-2", ...atributosCasilla, children: columnaFija.celda(fila) }),
2401
+ fijos.map((id, i) => {
2402
+ const col2 = porId.get(id);
2403
+ return /* @__PURE__ */ jsx(
2404
+ "td",
2405
+ {
2406
+ ...atributosFijo(i),
2407
+ className: `overflow-hidden px-3 py-2 align-top ${col2.alineado === "right" ? "text-right tabular-nums" : ""}`,
2408
+ children: col2.celda(fila)
2409
+ },
2410
+ id
2411
+ );
2412
+ }),
2413
+ idsVisibles.map((id) => {
2414
+ const col2 = porId.get(id);
2415
+ if (!col2) return null;
2416
+ return /* @__PURE__ */ jsx(
2417
+ "td",
2418
+ {
2419
+ className: `overflow-hidden px-3 py-2 align-top ${col2.alineado === "right" ? "text-right tabular-nums" : ""}`,
2420
+ children: col2.celda(fila)
2421
+ },
2422
+ id
2423
+ );
2424
+ }),
2425
+ columnaAcciones && /* @__PURE__ */ jsx("td", { className: "px-3 py-2", ...fija("der"), children: columnaAcciones.celda(fila) })
2426
+ ] }, claveFila(fila))) })
2427
+ ]
2428
+ }
2429
+ ) });
708
2430
  }
709
- var DownloadButton = forwardRef(function DownloadButton2({ label = "CSV", icon, type = "primary", children, ...rest }, ref) {
710
- return /* @__PURE__ */ jsx(Button, { ref, type, icon: icon ?? /* @__PURE__ */ jsx(DownloadOutlined, {}), ...rest, children: children ?? label });
711
- });
712
- function ConfirmModal({
713
- message,
714
- danger = false,
715
- okButtonProps,
716
- children,
717
- ...rest
2431
+ function EncabezadoOrdenable({
2432
+ columnaId,
2433
+ contenido,
2434
+ etiqueta,
2435
+ orden,
2436
+ onAlternar
2437
+ }) {
2438
+ const activa = orden?.id === columnaId;
2439
+ const dir = activa ? orden.dir : null;
2440
+ const Flecha = dir === "asc" ? ICONO_ORDENAR_ASC : dir === "desc" ? ICONO_ORDENAR_DESC : ICONO_ORDENAR_NEUTRO;
2441
+ const descripcionDir = dir === "asc" ? "ascendente" : dir === "desc" ? "descendente" : "sin ordenar";
2442
+ return /* @__PURE__ */ jsxs(
2443
+ "button",
2444
+ {
2445
+ type: "button",
2446
+ onClick: onAlternar,
2447
+ title: "Ordena solo la pagina visible (no todo el universo)",
2448
+ "aria-label": `Ordenar por ${etiqueta} en esta pagina (orden actual: ${descripcionDir})`,
2449
+ className: `inline-flex items-center gap-1 truncate font-medium transition hover:text-[var(--tema-primario)] ${activa ? "text-[var(--tema-primario)]" : ""}`,
2450
+ children: [
2451
+ contenido,
2452
+ /* @__PURE__ */ jsx(Flecha, { size: 12, "aria-hidden": "true", className: activa ? "" : "opacity-40" })
2453
+ ]
2454
+ }
2455
+ );
2456
+ }
2457
+ function ResizerColumna({
2458
+ anchoActual,
2459
+ onFinRedimension
718
2460
  }) {
2461
+ function onMouseDown(e) {
2462
+ e.preventDefault();
2463
+ e.stopPropagation();
2464
+ const inicioX = e.clientX;
2465
+ const inicioAncho = anchoActual;
2466
+ let ultimoAncho = anchoActual;
2467
+ function onMove(ev) {
2468
+ ultimoAncho = Math.max(60, inicioAncho + (ev.clientX - inicioX));
2469
+ onFinRedimension(ultimoAncho);
2470
+ }
2471
+ function onUp() {
2472
+ onFinRedimension(ultimoAncho);
2473
+ window.removeEventListener("mousemove", onMove);
2474
+ window.removeEventListener("mouseup", onUp);
2475
+ document.body.style.cursor = "";
2476
+ document.body.style.userSelect = "";
2477
+ }
2478
+ document.body.style.cursor = "col-resize";
2479
+ document.body.style.userSelect = "none";
2480
+ window.addEventListener("mousemove", onMove);
2481
+ window.addEventListener("mouseup", onUp);
2482
+ }
719
2483
  return /* @__PURE__ */ jsx(
720
- ModalDialog,
2484
+ "span",
721
2485
  {
722
- okButtonProps: { danger, ...okButtonProps },
723
- ...rest,
724
- children: children ?? (message ? /* @__PURE__ */ jsx("p", { children: message }) : null)
2486
+ role: "separator",
2487
+ "aria-label": "Redimensionar columna",
2488
+ "aria-orientation": "vertical",
2489
+ onMouseDown,
2490
+ draggable: false,
2491
+ onDragStart: (e) => e.preventDefault(),
2492
+ className: "absolute right-0 top-0 z-10 h-full w-2 cursor-col-resize select-none border-r-2 border-transparent hover:border-[var(--tema-primario)]"
725
2493
  }
726
2494
  );
727
2495
  }
2496
+ function EtiquetaCol({
2497
+ icono: Icono,
2498
+ texto,
2499
+ info
2500
+ }) {
2501
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
2502
+ Icono && /* @__PURE__ */ jsx(Icono, { size: TAMANO_BADGE, "aria-hidden": "true" }),
2503
+ texto,
2504
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
2505
+ ] });
2506
+ }
2507
+ function Campo({
2508
+ id,
2509
+ name,
2510
+ etiqueta,
2511
+ defaultValue,
2512
+ tipo = "text",
2513
+ icono: Icono
2514
+ }) {
2515
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2516
+ /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "inline-flex items-center gap-1 text-xs font-medium text-[#3c3c3b]", children: [
2517
+ Icono ? /* @__PURE__ */ jsx(Icono, { size: TAMANO_BADGE, "aria-hidden": "true" }) : null,
2518
+ etiqueta
2519
+ ] }),
2520
+ /* @__PURE__ */ jsx(
2521
+ "input",
2522
+ {
2523
+ id,
2524
+ name,
2525
+ type: tipo,
2526
+ defaultValue,
2527
+ className: "w-full rounded-md border border-[var(--tema-borde)] px-2 py-1.5 text-sm focus:border-[var(--tema-primario)] focus:outline-none"
2528
+ }
2529
+ )
2530
+ ] });
2531
+ }
2532
+ function CampoSelect({
2533
+ id,
2534
+ name,
2535
+ etiqueta,
2536
+ defaultValue,
2537
+ opciones,
2538
+ icono: Icono
2539
+ }) {
2540
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2541
+ /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "inline-flex items-center gap-1 text-xs font-medium text-[#3c3c3b]", children: [
2542
+ Icono ? /* @__PURE__ */ jsx(Icono, { size: TAMANO_BADGE, "aria-hidden": "true" }) : null,
2543
+ etiqueta
2544
+ ] }),
2545
+ /* @__PURE__ */ jsx(
2546
+ "select",
2547
+ {
2548
+ id,
2549
+ name,
2550
+ defaultValue,
2551
+ className: "w-full rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] px-2 py-1.5 text-sm focus:border-[var(--tema-primario)] focus:outline-none",
2552
+ children: opciones.map((o) => /* @__PURE__ */ jsx("option", { value: o.valor, children: o.etiqueta }, o.valor))
2553
+ }
2554
+ )
2555
+ ] });
2556
+ }
2557
+ function IconoConEtiqueta({ Icono, texto, info }) {
2558
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
2559
+ /* @__PURE__ */ jsx(Icono, { size: 16, "aria-hidden": "true" }),
2560
+ texto,
2561
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
2562
+ ] });
2563
+ }
2564
+ function MetricaKpi({
2565
+ etiqueta,
2566
+ valor,
2567
+ icono: Icono,
2568
+ destacada = false,
2569
+ sub,
2570
+ progreso,
2571
+ info
2572
+ }) {
2573
+ const claseTarjeta = destacada ? "rounded-lg border-2 border-[var(--tema-primario)] bg-[var(--tema-badge-neutro)] px-4 py-3" : "rounded-lg border border-[var(--tema-borde)] px-4 py-3";
2574
+ const claseEtiqueta = destacada ? "flex items-center gap-1 text-xs font-medium text-[var(--tema-primario)]" : "flex items-center gap-1 text-xs text-[#807f7f]";
2575
+ const claseValor = destacada ? "mt-1 text-2xl font-bold text-[var(--tema-primario-oscuro)]" : "mt-1 text-lg font-semibold text-[var(--tema-primario-oscuro)]";
2576
+ return /* @__PURE__ */ jsxs("div", { className: claseTarjeta, children: [
2577
+ /* @__PURE__ */ jsxs("p", { className: claseEtiqueta, children: [
2578
+ /* @__PURE__ */ jsx(Icono, { size: 12, "aria-hidden": "true" }),
2579
+ etiqueta,
2580
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
2581
+ ] }),
2582
+ /* @__PURE__ */ jsx("p", { className: claseValor, children: valor }),
2583
+ sub ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-[#807f7f]", children: sub }) : null,
2584
+ progreso ? /* @__PURE__ */ jsx(
2585
+ "div",
2586
+ {
2587
+ role: "progressbar",
2588
+ "aria-label": progreso.etiqueta,
2589
+ "aria-valuemin": 0,
2590
+ "aria-valuemax": 100,
2591
+ "aria-valuenow": progreso.porcentaje,
2592
+ className: "mt-2 h-1.5 w-full overflow-hidden rounded-full bg-[var(--tema-borde)]",
2593
+ children: /* @__PURE__ */ jsx(
2594
+ "div",
2595
+ {
2596
+ className: "h-full rounded-full bg-[var(--tema-primario)] transition-all",
2597
+ style: { width: `${progreso.porcentaje}%` }
2598
+ }
2599
+ )
2600
+ }
2601
+ ) : null
2602
+ ] });
2603
+ }
2604
+ function GrupoMetricas({ titulo, icono: Icono, children }) {
2605
+ return /* @__PURE__ */ jsxs("section", { className: "mt-4", children: [
2606
+ /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-[#807f7f]", children: [
2607
+ /* @__PURE__ */ jsx(Icono, { size: 13, "aria-hidden": "true" }),
2608
+ titulo
2609
+ ] }),
2610
+ /* @__PURE__ */ jsx("div", { className: "mt-2 grid grid-cols-2 gap-3 sm:grid-cols-4", children })
2611
+ ] });
2612
+ }
2613
+ function RejillaMetricas({ children }) {
2614
+ return /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-4", children });
2615
+ }
2616
+ function Rotulo({ texto, info }) {
2617
+ return /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-xs font-medium text-[#565656]", children: [
2618
+ /* @__PURE__ */ jsx("span", { children: texto }),
2619
+ /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
2620
+ ] });
2621
+ }
2622
+ function AccionesFila({
2623
+ etiqueta = "M\xE1s acciones",
2624
+ children
2625
+ }) {
2626
+ const acciones = Children.toArray(children).filter(
2627
+ (c) => isValidElement(c) || typeof c === "string" && c.trim() !== ""
2628
+ );
2629
+ const [abierto, setAbierto] = useState(false);
2630
+ const [pos, setPos] = useState({
2631
+ top: 0,
2632
+ right: 0
2633
+ });
2634
+ const boton = useRef(null);
2635
+ const menu = useRef(null);
2636
+ useEffect(() => {
2637
+ if (!abierto) return;
2638
+ const tecla = (e) => {
2639
+ if (e.key === "Escape") {
2640
+ setAbierto(false);
2641
+ boton.current?.focus();
2642
+ }
2643
+ };
2644
+ const fuera = (e) => {
2645
+ const t = e.target;
2646
+ if (!menu.current?.contains(t) && !boton.current?.contains(t))
2647
+ setAbierto(false);
2648
+ };
2649
+ const desplazar = (e) => {
2650
+ if (menu.current?.contains(e.target) || hayDialogo()) return;
2651
+ setAbierto(false);
2652
+ };
2653
+ document.addEventListener("keydown", tecla);
2654
+ document.addEventListener("mousedown", fuera);
2655
+ window.addEventListener("scroll", desplazar, true);
2656
+ return () => {
2657
+ document.removeEventListener("keydown", tecla);
2658
+ document.removeEventListener("mousedown", fuera);
2659
+ window.removeEventListener("scroll", desplazar, true);
2660
+ };
2661
+ }, [abierto]);
2662
+ function hayDialogo() {
2663
+ return Boolean(menu.current?.querySelector('[aria-modal="true"]'));
2664
+ }
2665
+ if (acciones.length <= 2) {
2666
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-1 whitespace-nowrap", children: acciones });
2667
+ }
2668
+ function alternar() {
2669
+ const r = boton.current?.getBoundingClientRect();
2670
+ if (r)
2671
+ setPos({
2672
+ top: r.bottom + 4,
2673
+ right: Math.max(8, window.innerWidth - r.right)
2674
+ });
2675
+ setAbierto((v) => !v);
2676
+ }
2677
+ return /* @__PURE__ */ jsxs("div", { className: "flex justify-end", children: [
2678
+ /* @__PURE__ */ jsx(
2679
+ "button",
2680
+ {
2681
+ ref: boton,
2682
+ type: "button",
2683
+ onClick: alternar,
2684
+ "aria-label": etiqueta,
2685
+ title: etiqueta,
2686
+ "aria-haspopup": "menu",
2687
+ "aria-expanded": abierto,
2688
+ className: "inline-flex items-center rounded-md border border-[var(--tema-borde)] p-1 text-[#565656] transition hover:bg-[var(--tema-badge-neutro)] hover:text-[var(--tema-primario)]",
2689
+ children: /* @__PURE__ */ jsx(ICONO_ACCIONES_FILA, { size: TAMANO_BADGE, "aria-hidden": "true" })
2690
+ }
2691
+ ),
2692
+ abierto && createPortal(
2693
+ /* @__PURE__ */ jsx(
2694
+ "div",
2695
+ {
2696
+ ref: menu,
2697
+ role: "menu",
2698
+ "aria-label": etiqueta,
2699
+ style: { top: pos.top, right: pos.right },
2700
+ onClickCapture: () => setTimeout(() => {
2701
+ if (!hayDialogo()) setAbierto(false);
2702
+ }, 0),
2703
+ className: "fixed z-50 flex min-w-40 flex-col items-stretch gap-1 rounded-md border border-[var(--tema-borde)] bg-[var(--tema-fondo)] p-1.5 text-left shadow-lg [&>*]:justify-start",
2704
+ children: acciones
2705
+ }
2706
+ ),
2707
+ document.body
2708
+ )
2709
+ ] });
2710
+ }
2711
+ function BotonAccionFila({
2712
+ icono: Icono,
2713
+ etiqueta,
2714
+ ariaLabel,
2715
+ onClick,
2716
+ disabled = false,
2717
+ info
2718
+ }) {
2719
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
2720
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className: CLASE_ACCION_FILA, children: [
2721
+ /* @__PURE__ */ jsx(Icono, { size: 12, "aria-hidden": "true" }),
2722
+ etiqueta
2723
+ ] }),
2724
+ info && /* @__PURE__ */ jsx(InfoExplicacion, { id: info })
2725
+ ] });
2726
+ }
728
2727
 
729
- export { AmsConfigProvider, Button, Card, ConfirmModal, DataTable, DownloadButton, FileUploadDragger, FilterSelect, ModalDialog, PageHeader, Pagination, Tag, ThemeToggle, amsTheme, amsThemeDark, applyThemeModeToDOM, buildTheme, getInitialThemeMode, primitives, radius, semantic, sizing, typography, useAmsMessage, useAmsNotification, useControllableState, useThemeMode };
2728
+ export { AccionConDialogo, AccionesFila, Aviso, AvisoAccionEstado, BarraListado, BarraProgreso, BotonAccionAviso, BotonAccionFila, CLASE_ACCION_FILA, CLASE_AYUDA_CAMPO, CLASE_BOTON_CONTORNO, CLASE_BOTON_ICONO, CLASE_BOTON_PELIGRO, CLASE_BOTON_PEQUENO, CLASE_BOTON_PRIMARIO, CLASE_BOTON_PRIMARIO_COMPACTO, CLASE_BOTON_SECUNDARIO, CLASE_CAMPO, CLASE_CAMPO_COMPACTO, CLASE_ERROR_CAMPO, CLASE_ETIQUETA_CAMPO, CLASE_EXITO_CAMPO, CLASE_REGION_LISTADO, CLASE_TARJETA, CLASE_TEXTO_ATENUADO, CLASE_TITULO_PAGINA, CLASE_TITULO_SECCION, Campo, CampoSelect, CascaronApp, CeldaIdentidad, ETIQUETAS_SEMAFORO, EXPLICACIONES, EncabezadoOrdenUrl, EnlaceNav, EstadoVacio, EstadoVacioFila, EtiquetaCol, FechaHoraLocal, GrupoMetricas, ICONOS_ESTATUS, ICONOS_SEMAFORO, ICONO_ACCIONES_FILA, ICONO_ALTA, ICONO_API_KEY, ICONO_ARRASTRAR_COLUMNA, ICONO_AVISO, ICONO_BAJA, ICONO_BUSCAR, ICONO_CAMPANA, ICONO_CARGANDO, ICONO_CERRAR, ICONO_CERRAR_SESION, ICONO_COLUMNAS, ICONO_COLUMNA_OCULTA, ICONO_COLUMNA_VISIBLE, ICONO_COL_CODIGO, ICONO_COL_DOCUMENTO, ICONO_COL_ESTADO, ICONO_COL_ETIQUETA, ICONO_COL_FECHA, ICONO_COL_FECHA_HORA, ICONO_COL_MECANISMO, ICONO_COL_NOMBRE, ICONO_COL_TOTAL, ICONO_COL_USUARIO, ICONO_CONFIRMAR, ICONO_COPIAR, ICONO_DEFAULT, ICONO_DENSIDAD_COMODA, ICONO_DENSIDAD_COMPACTA, ICONO_DESCARGAR, ICONO_DESHABILITAR, ICONO_DURACION, ICONO_EDITAR, ICONO_EMAIL, ICONO_ERROR, ICONO_EXPORTAR, ICONO_EXPORTAR_CSV, ICONO_FILTRO, ICONO_GENERAR, ICONO_GUARDAR, ICONO_INACTIVAR, ICONO_INFO_EXPLICACION, ICONO_INICIAR_SESION, ICONO_INVITACION_GENERICA, ICONO_KPI_ALERTA, ICONO_KPI_BLOQUEO, ICONO_KPI_CONSUMO, ICONO_KPI_CONTEO, ICONO_KPI_CREDENCIALES, ICONO_KPI_RENDIMIENTO, ICONO_KPI_RITMO, ICONO_LIMPIAR, ICONO_LINK, ICONO_LOGIN_MICROSOFT, ICONO_NAV_ACCESOS, ICONO_NAV_ANALITICA, ICONO_NAV_API_KEYS, ICONO_NAV_BITACORA, ICONO_NAV_CLIENTE, ICONO_NAV_COLAPSAR, ICONO_NAV_CONFIGURACION, ICONO_NAV_DASHBOARD, ICONO_NAV_ENTIDADES, ICONO_NAV_ETIQUETAS, ICONO_NAV_EXPANDIR, ICONO_NAV_EXTRACCIONES, ICONO_NAV_INVITACIONES, ICONO_NAV_JOBS, ICONO_NAV_MANUAL, ICONO_NAV_MENU_MOVIL, ICONO_NAV_NOTIFICACIONES, ICONO_NAV_PAQUETES, ICONO_NAV_PLANES, ICONO_NAV_SALUD, ICONO_NAV_TEMA, ICONO_NAV_USUARIOS, ICONO_OK, ICONO_ORDENAR_ASC, ICONO_ORDENAR_DESC, ICONO_ORDENAR_NEUTRO, ICONO_PAGINA_ANTERIOR, ICONO_PAGINA_SIGUIENTE, ICONO_PERIODO, ICONO_REACTIVAR, ICONO_REACTIVAR_ETIQUETA, ICONO_REINTENTAR, ICONO_RESTABLECER, ICONO_REVOCAR_KEY, ICONO_ROTAR_KEY, ICONO_SEGURIDAD, ICONO_SELECCIONADO, ICONO_SILENCIAR, ICONO_UBICACION, ICONO_VER_TODOS, ICONO_VIGENCIA, ICONO_VISTA_TABLA, ICONO_VISTA_TARJETAS, ICONO_VOLVER, IconoConEtiqueta, InfoExplicacion, Insignia, LinkBase, LinkComponentProvider, ListadoNavProvider, MarcoTabla, Metrica, MetricaKpi, Migas, ModalPanel, NOMBRE_PRODUCTO_DEFAULT, NO_DISPONIBLE, NoDisponible, OverlayCargando, PaginaContenido, PaginaListado, PaginadorLocal, PaginadorUrl, PantallaAcceso, PantallaCargando, Pestanas, PopoverHover, RejillaMetricas, Rotulo, SeccionNav, SidebarColapsable, SpinnerMarca, SubNav, TAMANOS_LISTADO, TAMANO_BADGE, TAMANO_BOTON, TAMANO_LISTADO_DEFAULT, TEMA_DEFAULT, TablaConfigurable, TablaPlana, Tarjeta, ThInfo, UsuarioTablas, aplicarListado, calcularPosicionPopover, col, etiquetaSemaforo, explicacionDe, fija, formatearBytes, formatearFechaHora, formatearFechaHoraCDMX, formatearRangoFechaHora, formatoDias, formatoEntero, formatoFecha, formatoMes, formatoMxn, formatoNumero, formatoPct, hrefListado, iconoDeEstatus, iconoDeSemaforo, leerListado, normalizarTexto, useAccionAviso, useAccionConSpinner, useNavegarListado, usePaginacionLocal, useTablaPreferencias, variablesDeTema, verificarCatalogo };
730
2729
  //# sourceMappingURL=index.js.map
731
2730
  //# sourceMappingURL=index.js.map