frontend-plus-react 0.6.0 → 0.8.0

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
@@ -317,7 +317,7 @@ var useApiCallWithoutSnackbar = () => {
317
317
  };
318
318
 
319
319
  // src/components/grid/GenericDataGrid.tsx
320
- import { CircularProgress as CircularProgress4, Typography as Typography10, Box as Box12, Alert as Alert2, useTheme as useTheme4, Dialog as Dialog4 } from "@mui/material";
320
+ import { CircularProgress as CircularProgress4, Typography as Typography11, Box as Box13, Alert as Alert2, useTheme as useTheme4, Dialog as Dialog5 } from "@mui/material";
321
321
 
322
322
  // src/store/index.ts
323
323
  import { configureStore, combineReducers } from "@reduxjs/toolkit";
@@ -404,8 +404,9 @@ import { createSlice as createSlice2 } from "@reduxjs/toolkit";
404
404
  var initialState2 = {
405
405
  currentPath: "/",
406
406
  previousPath: null,
407
- redirectPath: null
407
+ redirectPath: null,
408
408
  // <--- Inicializamos en null
409
+ redirectUsername: null
409
410
  };
410
411
  var routerSlice = createSlice2({
411
412
  name: "router",
@@ -417,15 +418,20 @@ var routerSlice = createSlice2({
417
418
  },
418
419
  setRedirectPath: (state, action) => {
419
420
  state.redirectPath = action.payload;
421
+ if (action.payload === null) state.redirectUsername = null;
422
+ },
423
+ setRedirectUsername: (state, action) => {
424
+ state.redirectUsername = action.payload;
420
425
  },
421
426
  resetRouterState: (state) => {
422
427
  state.currentPath = "/";
423
428
  state.previousPath = null;
424
429
  state.redirectPath = null;
430
+ state.redirectUsername = null;
425
431
  }
426
432
  }
427
433
  });
428
- var { setCurrentPath, setRedirectPath, resetRouterState } = routerSlice.actions;
434
+ var { setCurrentPath, setRedirectPath, setRedirectUsername, resetRouterState } = routerSlice.actions;
429
435
  var routerSlice_default = routerSlice.reducer;
430
436
 
431
437
  // src/store/menuUiSlice.ts
@@ -450,13 +456,23 @@ var menuUiSlice = createSlice3({
450
456
  toggleSubMenu: (state, action) => {
451
457
  state.subMenuOpenStates[action.payload] = !state.subMenuOpenStates[action.payload];
452
458
  },
453
- // --- NUEVO REDUCER: Establecer el estado de todos los submenús ---
454
- setAllSubMenusOpen: (state, action) => {
455
- for (const menuName in state.subMenuOpenStates) {
456
- if (Object.prototype.hasOwnProperty.call(state.subMenuOpenStates, menuName)) {
457
- state.subMenuOpenStates[menuName] = action.payload;
459
+ /** Acordeón: abre un grupo y cierra a sus hermanos (con todo lo que tienen adentro). */
460
+ abrirGrupo: (state, action) => {
461
+ for (const clave of Object.keys(state.subMenuOpenStates)) {
462
+ if (action.payload.hermanos.some((hermano) => clave === hermano || clave.startsWith(hermano + "/"))) {
463
+ state.subMenuOpenStates[clave] = false;
458
464
  }
459
465
  }
466
+ state.subMenuOpenStates[action.payload.clave] = true;
467
+ },
468
+ /** Deja abiertos solo los grupos de este camino (el del ítem que se está mostrando). */
469
+ abrirCamino: (state, action) => {
470
+ for (const clave of Object.keys(state.subMenuOpenStates)) {
471
+ state.subMenuOpenStates[clave] = action.payload.includes(clave);
472
+ }
473
+ for (const clave of action.payload) {
474
+ state.subMenuOpenStates[clave] = true;
475
+ }
460
476
  },
461
477
  resetMenuUiState: (state) => {
462
478
  Object.assign(state, initialState3);
@@ -468,8 +484,8 @@ var {
468
484
  toggleDrawer,
469
485
  setSubMenuOpen,
470
486
  toggleSubMenu,
471
- setAllSubMenusOpen,
472
- // ¡Exportamos la nueva acción!
487
+ abrirGrupo,
488
+ abrirCamino,
473
489
  resetMenuUiState
474
490
  } = menuUiSlice.actions;
475
491
  var menuUiSlice_default = menuUiSlice.reducer;
@@ -1377,7 +1393,7 @@ function BotonDetalle({ row, rowId, detailTable, deshabilitado }) {
1377
1393
  const { abiertos, alternarDetalle } = useDetallesGrilla();
1378
1394
  if (!cumpleCondicion(detailTable.condition, row)) return /* @__PURE__ */ jsx5("div", { className: "fpr-boton-detalle" });
1379
1395
  const abierto = abiertos.has(claveDetalle(rowId, detailTable));
1380
- const titulo = deshabilitado ? "Grab\xE1 la fila para ver sus detalles" : `${detailTable.label || detailTable.abr}${abierto ? " (replegar)" : ""} \u2014 Ctrl+click: en otra pesta\xF1a`;
1396
+ const titulo = deshabilitado ? "Grab\xE1 la fila para ver sus detalles" : "";
1381
1397
  return /* @__PURE__ */ jsx5(Tooltip, { title: titulo, children: /* @__PURE__ */ jsx5("span", { className: "fpr-boton-detalle", children: /* @__PURE__ */ jsx5(
1382
1398
  IconButton,
1383
1399
  {
@@ -1813,7 +1829,8 @@ var useGridMenu = ({
1813
1829
  callApi,
1814
1830
  resetSorting,
1815
1831
  triggerImport,
1816
- triggerExport
1832
+ triggerExport,
1833
+ abrirColumnas
1817
1834
  }) => {
1818
1835
  const { showSuccess, showError, showWarning } = useSnackbar();
1819
1836
  const menuOptions = useMemo4(() => {
@@ -1849,7 +1866,7 @@ var useGridMenu = ({
1849
1866
  label: "ocultar o mostrar columnas",
1850
1867
  icon: React4.createElement(VisibilityOffIcon),
1851
1868
  handler: async () => {
1852
- console.log("Ocultar/mostrar columnas");
1869
+ abrirColumnas?.();
1853
1870
  },
1854
1871
  visible: true
1855
1872
  });
@@ -1898,7 +1915,7 @@ var useGridMenu = ({
1898
1915
  visible: true
1899
1916
  });
1900
1917
  return options;
1901
- }, [tableDefinition, tableName, fixedFields, setTableData, callApi, resetSorting, triggerImport, triggerExport, showSuccess, showError, showWarning]);
1918
+ }, [tableDefinition, tableName, fixedFields, setTableData, callApi, resetSorting, triggerImport, triggerExport, abrirColumnas, showSuccess, showError, showWarning]);
1902
1919
  return { menuOptions };
1903
1920
  };
1904
1921
 
@@ -1969,7 +1986,7 @@ var useVistaGrilla = () => useContext3(VistaGrillaContext);
1969
1986
 
1970
1987
  // src/components/grid/renderers/headerCellRenderers.tsx
1971
1988
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1972
- var defaultColumnHeaderCellRenderer = (props, fieldDef) => {
1989
+ var defaultColumnHeaderCellRenderer = (props, fieldDef, ocultarColumna) => {
1973
1990
  const { column, sortDirection, priority } = props;
1974
1991
  const isPrimaryKey = fieldDef.isPk;
1975
1992
  const sorted = sortDirection !== void 0;
@@ -1984,6 +2001,11 @@ var defaultColumnHeaderCellRenderer = (props, fieldDef) => {
1984
2001
  return /* @__PURE__ */ jsxs5(
1985
2002
  Box3,
1986
2003
  {
2004
+ onClickCapture: (e) => {
2005
+ if (!e.altKey || !ocultarColumna) return;
2006
+ e.stopPropagation();
2007
+ ocultarColumna(fieldDef.name);
2008
+ },
1987
2009
  sx: {
1988
2010
  display: "flex",
1989
2011
  alignItems: "center",
@@ -1999,9 +2021,10 @@ var defaultColumnHeaderCellRenderer = (props, fieldDef) => {
1999
2021
  {
2000
2022
  variant: "body2",
2001
2023
  sx: {
2002
- fontWeight: "bold",
2024
+ fontWeight: 500,
2025
+ color: "#37474f",
2003
2026
  textDecoration: isPrimaryKey ? "underline" : "none",
2004
- fontSize: "0.930rem",
2027
+ fontSize: "0.85rem",
2005
2028
  textAlign: "left",
2006
2029
  flexGrow: 1,
2007
2030
  overflow: "hidden",
@@ -2027,22 +2050,20 @@ var ActionsColumnHeaderCell = ({
2027
2050
  allowInsert && /* @__PURE__ */ jsx6(
2028
2051
  IconButton2,
2029
2052
  {
2030
- color: "inherit",
2031
2053
  onClick: () => handleAddRow(),
2032
2054
  size: "small",
2033
2055
  title: "A\xF1adir registro",
2034
- sx: { p: 0.25 },
2035
- children: /* @__PURE__ */ jsx6(AddIcon, { sx: { fontSize: 18, color: "success.main" } })
2056
+ sx: { p: 0.25, color: "grey.800", "&:hover": { color: "success.main" } },
2057
+ children: /* @__PURE__ */ jsx6(AddIcon, { sx: { fontSize: 18 } })
2036
2058
  }
2037
2059
  ),
2038
2060
  /* @__PURE__ */ jsx6(
2039
2061
  IconButton2,
2040
2062
  {
2041
- color: "inherit",
2042
2063
  onClick: toggleFilterVisibility,
2043
2064
  size: "small",
2044
2065
  title: isFilterRowVisible ? "Ocultar filtros" : "Mostrar filtros",
2045
- sx: { p: 0.25 },
2066
+ sx: { p: 0.25, color: "grey.800" },
2046
2067
  children: isFilterRowVisible ? /* @__PURE__ */ jsx6(SearchOffIcon, { sx: { fontSize: 18 } }) : /* @__PURE__ */ jsx6(SearchIcon, { sx: { fontSize: 18 } })
2047
2068
  }
2048
2069
  ),
@@ -2052,7 +2073,7 @@ var ActionsColumnHeaderCell = ({
2052
2073
  onClick: (event) => handleDataGridOptions(event),
2053
2074
  size: "small",
2054
2075
  title: "Opciones de esta tabla",
2055
- sx: { p: 0.25 },
2076
+ sx: { p: 0.25, color: "grey.800" },
2056
2077
  children: /* @__PURE__ */ jsx6(MoreVertIcon, { sx: { fontSize: 20, variant: "filled", size: "small" } })
2057
2078
  }
2058
2079
  )
@@ -2542,9 +2563,12 @@ var TypedField = ({ fieldDef, value, onChange, onCommit, disabled, isFilterMode
2542
2563
  "& .MuiOutlinedInput-root": {
2543
2564
  backgroundColor: "background.paper",
2544
2565
  height: FILTER_ROW_HEIGHT - 5,
2545
- minWidth: 50,
2566
+ minWidth: 30,
2546
2567
  fontSize: "0.8rem",
2547
2568
  padding: "0px"
2569
+ },
2570
+ "& .MuiOutlinedInput-input": {
2571
+ padding: "0 4px"
2548
2572
  }
2549
2573
  } : {
2550
2574
  "& .MuiOutlinedInput-root": {
@@ -2591,36 +2615,42 @@ function FilterInputRenderer({
2591
2615
  [columnKey]: { operator: newOp, value: formattedVal }
2592
2616
  }));
2593
2617
  };
2594
- return /* @__PURE__ */ jsxs10(Box4, { sx: { mt: 0, display: "flex", alignItems: "center", gap: 0.5, pr: 1 }, children: [
2595
- /* @__PURE__ */ jsx12(
2596
- "select",
2597
- {
2598
- value: operator,
2599
- onChange: (e) => handleFilterChange(e.target.value, val),
2600
- style: {
2601
- height: FILTER_ROW_HEIGHT - 5,
2602
- minWidth: 40,
2603
- backgroundColor: "white",
2604
- border: "1px solid #ccc",
2605
- borderRadius: "4px",
2606
- fontSize: "0.8rem",
2607
- padding: "0 2px",
2608
- textAlign: "center",
2609
- cursor: "pointer"
2610
- },
2611
- children: filterOperators.map((op) => /* @__PURE__ */ jsx12("option", { value: op.value, children: op.label }, op.value))
2612
- }
2613
- ),
2614
- /* @__PURE__ */ jsx12(
2615
- TypedField,
2616
- {
2617
- fieldDef,
2618
- isFilterMode: true,
2619
- value: val,
2620
- onChange: (_name, newValue, _isValid) => handleFilterChange(operator, newValue)
2621
- }
2622
- )
2623
- ] });
2618
+ return (
2619
+ // como en backend-plus, el operador es un símbolo chico y el campo se queda con el resto del ancho
2620
+ /* @__PURE__ */ jsxs10(Box4, { sx: { mt: 0, display: "flex", alignItems: "center", gap: "2px", width: "100%" }, children: [
2621
+ /* @__PURE__ */ jsx12(
2622
+ "select",
2623
+ {
2624
+ value: operator,
2625
+ onChange: (e) => handleFilterChange(e.target.value, val),
2626
+ style: {
2627
+ height: FILTER_ROW_HEIGHT - 5,
2628
+ width: 22,
2629
+ flexShrink: 0,
2630
+ appearance: "none",
2631
+ backgroundColor: "#CEF",
2632
+ border: "none",
2633
+ borderRadius: "4px",
2634
+ fontSize: "0.8rem",
2635
+ padding: 0,
2636
+ textAlign: "center",
2637
+ textAlignLast: "center",
2638
+ cursor: "pointer"
2639
+ },
2640
+ children: filterOperators.map((op) => /* @__PURE__ */ jsx12("option", { value: op.value, children: op.label }, op.value))
2641
+ }
2642
+ ),
2643
+ /* @__PURE__ */ jsx12(
2644
+ TypedField,
2645
+ {
2646
+ fieldDef,
2647
+ isFilterMode: true,
2648
+ value: val,
2649
+ onChange: (_name, newValue, _isValid) => handleFilterChange(operator, newValue)
2650
+ }
2651
+ )
2652
+ ] })
2653
+ );
2624
2654
  }
2625
2655
  return /* @__PURE__ */ jsx12(
2626
2656
  InputBase3,
@@ -2656,7 +2686,7 @@ var defaultColumnSummaryCellRenderer = (props, fixedFields) => {
2656
2686
  const { filters, setFilters } = row;
2657
2687
  if (valorFijoDe(fixedFields, column.key)) return null;
2658
2688
  const fieldDef = esColumnaPropia(column) && column.customType === "default" ? column.fieldDef : void 0;
2659
- return /* @__PURE__ */ jsx13(Box5, { sx: { width: "100%", height: "100%", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx13(
2689
+ return /* @__PURE__ */ jsx13(Box5, { sx: { position: "absolute", inset: 0, display: "flex", alignItems: "center", px: "2px" }, children: /* @__PURE__ */ jsx13(
2660
2690
  FilterInputRender_default,
2661
2691
  {
2662
2692
  columnKey: column.key,
@@ -2717,7 +2747,7 @@ var detailColumnCellSummaryRenderer = (_props) => null;
2717
2747
 
2718
2748
  // src/components/grid/renderers/cellRenderers.tsx
2719
2749
  import React11 from "react";
2720
- import { Box as Box8, Button as Button4, Tooltip as Tooltip4, useTheme as useTheme2 } from "@mui/material";
2750
+ import { Box as Box8, IconButton as IconButton5, Tooltip as Tooltip4, useTheme as useTheme2 } from "@mui/material";
2721
2751
  import ViewHeadlineIcon from "@mui/icons-material/ViewHeadline";
2722
2752
  import DeleteIcon from "@mui/icons-material/Delete";
2723
2753
  import AddIcon2 from "@mui/icons-material/Add";
@@ -2883,14 +2913,12 @@ var allColumnsCellRenderer = (props) => {
2883
2913
  }
2884
2914
  ];
2885
2915
  return /* @__PURE__ */ jsx16(Box8, { sx: { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", gap: 0.5 }, children: gridActionButtons.map((actionDef) => tableDefinition.allow[actionDef.action] ? /* @__PURE__ */ jsx16(
2886
- Button4,
2916
+ IconButton5,
2887
2917
  {
2888
- variant: "outlined",
2889
- color: actionDef.color,
2890
2918
  size: "small",
2891
2919
  onClick: () => actionDef.handler(row),
2892
2920
  title: actionDef.title,
2893
- sx: { width: 22, minWidth: 22, height: 22, "& .MuiButton-startIcon": { m: 0 } },
2921
+ sx: { width: 22, height: 22, p: 0, color: "grey.800", "&:hover": { color: `${actionDef.color}.main` } },
2894
2922
  children: /* @__PURE__ */ jsx16(actionDef.icon, { sx: { fontSize: 18 } })
2895
2923
  },
2896
2924
  actionDef.action
@@ -3290,22 +3318,46 @@ var allColumnsEditCellRenderer = (props, allColumns) => {
3290
3318
  }
3291
3319
  };
3292
3320
 
3321
+ // src/utils/columnasOcultas.ts
3322
+ function columnasOcultasIniciales(tableDefinition) {
3323
+ return Array.from(new Set(tableDefinition.hiddenColumns ?? []));
3324
+ }
3325
+ function campoDibujable(field) {
3326
+ return field.visible !== false;
3327
+ }
3328
+
3293
3329
  // src/hooks/grid/useGridColumns.ts
3294
3330
  var estimateTextWidth = (text) => text.length * 8 + 16;
3295
- var calcularAnchosDeColumnas = (tableDefinition, filas) => {
3331
+ var contextoDeMedida;
3332
+ var anchoDeTitulo = (texto) => {
3333
+ if (contextoDeMedida === void 0) {
3334
+ try {
3335
+ contextoDeMedida = document.createElement("canvas").getContext("2d");
3336
+ } catch {
3337
+ contextoDeMedida = null;
3338
+ }
3339
+ }
3340
+ if (!contextoDeMedida) return texto.length * 9;
3341
+ contextoDeMedida.font = "bold 14.9px Roboto, Helvetica, Arial, sans-serif";
3342
+ return Math.ceil(contextoDeMedida.measureText(texto).width);
3343
+ };
3344
+ var EXTRA_TITULO = 8 + 20 + 4;
3345
+ var calcularAnchosDeColumnas = (tableDefinition, filas, anchoDisponible = Infinity) => {
3296
3346
  const anchos = /* @__PURE__ */ new Map();
3347
+ const anchosConTituloCompleto = /* @__PURE__ */ new Map();
3297
3348
  const muestra = filas.slice(0, 50);
3298
3349
  tableDefinition.fields.forEach((fieldDef) => {
3299
3350
  if (typeof fieldDef.width === "number" && fieldDef.width > 0) {
3300
3351
  anchos.set(fieldDef.name, fieldDef.width);
3352
+ anchosConTituloCompleto.set(fieldDef.name, fieldDef.width);
3301
3353
  return;
3302
3354
  }
3303
3355
  const headerLabel = fieldDef.label || fieldDef.name;
3304
3356
  const type = fieldDef.typeName?.toLowerCase() || "";
3305
- let typeMinWidth = 60;
3306
- if (type.includes("boolean") || type.includes("checkbox")) typeMinWidth = 60;
3307
- else if (type.includes("date") || type.includes("timestamp")) typeMinWidth = 110;
3308
- else if (type.includes("integer") || type.includes("numeric")) typeMinWidth = 80;
3357
+ let typeMinWidth = 80;
3358
+ if (type.includes("boolean") || type.includes("checkbox")) typeMinWidth = 80;
3359
+ else if (type.includes("date") || type.includes("timestamp")) typeMinWidth = 120;
3360
+ else if (type.includes("integer") || type.includes("numeric")) typeMinWidth = 90;
3309
3361
  const headerWidth = estimateTextWidth(headerLabel) + 20;
3310
3362
  const typer = typerFrom(fieldDef);
3311
3363
  const maxDataWidth = muestra.reduce((maxW, row) => {
@@ -3319,14 +3371,19 @@ var calcularAnchosDeColumnas = (tableDefinition, filas) => {
3319
3371
  }
3320
3372
  return Math.max(maxW, estimateTextWidth(texto));
3321
3373
  }, 0);
3322
- anchos.set(fieldDef.name, Math.min(Math.max(headerWidth, maxDataWidth, typeMinWidth), 500));
3374
+ const compacto = Math.min(Math.max(headerWidth, maxDataWidth, typeMinWidth), 500);
3375
+ anchos.set(fieldDef.name, compacto);
3376
+ anchosConTituloCompleto.set(fieldDef.name, Math.max(compacto, Math.min(anchoDeTitulo(cambiarGuionesBajosPorEspacios(headerLabel)) + EXTRA_TITULO, 500)));
3323
3377
  });
3324
- return anchos;
3378
+ const sumaCompleta = [...anchos.keys()].reduce((total, nombre) => total + (anchosConTituloCompleto.get(nombre) ?? 0), 0);
3379
+ return sumaCompleta <= anchoDisponible ? anchosConTituloCompleto : anchos;
3325
3380
  };
3326
3381
  var useGridColumns = ({
3327
3382
  tableDefinition,
3328
3383
  anchosCalculados,
3329
3384
  fixedFields,
3385
+ columnasOcultas,
3386
+ ocultarColumna,
3330
3387
  primaryKey,
3331
3388
  setTableData,
3332
3389
  handleKeyPressInEditor,
@@ -3340,7 +3397,7 @@ var useGridColumns = ({
3340
3397
  const columns = useMemo8(() => {
3341
3398
  if (!tableDefinition) return [];
3342
3399
  const fieldsToShow = tableDefinition.fields.filter((field) => {
3343
- return !columnaOcultaPorFixedField(fixedFields, field.name) && field.visible !== false;
3400
+ return campoDibujable(field) && !columnaOcultaPorFixedField(fixedFields, field.name) && !columnasOcultas.has(field.name);
3344
3401
  });
3345
3402
  const defaultColumns = fieldsToShow.map((fieldDef) => {
3346
3403
  const isFixedField = valorFijoDe(fixedFields, fieldDef.name) !== void 0;
@@ -3365,7 +3422,7 @@ var useGridColumns = ({
3365
3422
  registrarEditor,
3366
3423
  width: colWidth,
3367
3424
  minWidth: 40,
3368
- renderHeaderCell: (props) => defaultColumnHeaderCellRenderer(props, fieldDef),
3425
+ renderHeaderCell: (props) => defaultColumnHeaderCellRenderer(props, fieldDef, ocultarColumna),
3369
3426
  renderSummaryCell: (props) => defaultColumnSummaryCellRenderer(props, fixedFields)
3370
3427
  };
3371
3428
  return column;
@@ -3428,7 +3485,7 @@ var useGridColumns = ({
3428
3485
  renderEditCell: (props) => allColumnsEditCellRenderer(props, allColumns),
3429
3486
  renderCell: (props) => allColumnsCellRenderer(props)
3430
3487
  }));
3431
- }, [tableDefinition, anchosCalculados, fixedFields, primaryKey, setTableData, handleKeyPressInEditor, registrarEditor, handleDeleteRow, handleAddRow, handleVerticalEditRow, toggleFilterVisibility, abrirOpcionesDeGrilla]);
3488
+ }, [tableDefinition, anchosCalculados, fixedFields, columnasOcultas, ocultarColumna, primaryKey, setTableData, handleKeyPressInEditor, registrarEditor, handleDeleteRow, handleAddRow, handleVerticalEditRow, toggleFilterVisibility, abrirOpcionesDeGrilla]);
3432
3489
  return { columns };
3433
3490
  };
3434
3491
 
@@ -3439,7 +3496,7 @@ import {
3439
3496
  DialogTitle as DialogTitle2,
3440
3497
  DialogContent as DialogContent2,
3441
3498
  DialogActions as DialogActions2,
3442
- Button as Button5,
3499
+ Button as Button4,
3443
3500
  FormControlLabel,
3444
3501
  Checkbox,
3445
3502
  Box as Box9,
@@ -3563,9 +3620,9 @@ var ImportDialog = ({ open, onClose, onImport, loading }) => {
3563
3620
  ] })
3564
3621
  ] }) }),
3565
3622
  /* @__PURE__ */ jsxs14(DialogActions2, { sx: { px: 3, py: 2 }, children: [
3566
- /* @__PURE__ */ jsx19(Button5, { onClick: onClose, disabled: loading, children: "Cancelar" }),
3623
+ /* @__PURE__ */ jsx19(Button4, { onClick: onClose, disabled: loading, children: "Cancelar" }),
3567
3624
  /* @__PURE__ */ jsx19(
3568
- Button5,
3625
+ Button4,
3569
3626
  {
3570
3627
  onClick: handleConfirm,
3571
3628
  variant: "contained",
@@ -3585,7 +3642,7 @@ import {
3585
3642
  DialogTitle as DialogTitle3,
3586
3643
  DialogContent as DialogContent3,
3587
3644
  DialogActions as DialogActions3,
3588
- Button as Button6,
3645
+ Button as Button5,
3589
3646
  FormControl as FormControl2,
3590
3647
  FormLabel,
3591
3648
  RadioGroup,
@@ -3604,6 +3661,7 @@ var ExportDialog = ({
3604
3661
  open,
3605
3662
  onClose,
3606
3663
  tableDefinition,
3664
+ columnasOcultas,
3607
3665
  tableData,
3608
3666
  filteredRows,
3609
3667
  username = "anonymous"
@@ -3634,8 +3692,7 @@ var ExportDialog = ({
3634
3692
  };
3635
3693
  const executeExport = async () => {
3636
3694
  const fieldsDef2Export = tableDefinition.fields.filter((fieldDef) => {
3637
- const isVisible = fieldDef.visible !== false;
3638
- return (fieldDef.inTable !== false || options.fromOtherTables) && isVisible && (fieldDef.editable !== false || options.readOnly || tableDefinition.primaryKey?.includes(fieldDef.name)) && (isVisible || options.hiddens);
3695
+ return (fieldDef.inTable !== false || options.fromOtherTables) && campoDibujable(fieldDef) && (fieldDef.editable !== false || options.readOnly || tableDefinition.primaryKey?.includes(fieldDef.name)) && (!columnasOcultas.has(fieldDef.name) || options.hiddens);
3639
3696
  });
3640
3697
  if (format === "xlsx") {
3641
3698
  await excelExport(fieldsDef2Export);
@@ -3793,32 +3850,52 @@ var ExportDialog = ({
3793
3850
  ] })
3794
3851
  ] }) }),
3795
3852
  /* @__PURE__ */ jsxs15(DialogActions3, { children: [
3796
- /* @__PURE__ */ jsx20(Button6, { onClick: onClose, disabled: isPreparing, children: "Cancelar" }),
3797
- /* @__PURE__ */ jsx20(Button6, { onClick: handlePrepareExport, variant: "contained", disabled: isPreparing, children: "Descargar" })
3853
+ /* @__PURE__ */ jsx20(Button5, { onClick: onClose, disabled: isPreparing, children: "Cancelar" }),
3854
+ /* @__PURE__ */ jsx20(Button5, { onClick: handlePrepareExport, variant: "contained", disabled: isPreparing, children: "Descargar" })
3798
3855
  ] })
3799
3856
  ] });
3800
3857
  };
3801
3858
 
3859
+ // src/components/grid/DialogoDeColumnas.tsx
3860
+ import { Box as Box11, Button as Button6, Dialog as Dialog4, DialogActions as DialogActions4, DialogContent as DialogContent4, DialogTitle as DialogTitle4, List as List3, ListItemButton as ListItemButton3, ListItemText as ListItemText2, Typography as Typography9 } from "@mui/material";
3861
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3862
+ var DialogoDeColumnas = ({ open, onClose, columnas, ocultas, alternarColumna }) => {
3863
+ const lista = (titulo, nombres, idDeLista) => /* @__PURE__ */ jsxs16(Box11, { sx: { flex: 1, minWidth: 0 }, children: [
3864
+ /* @__PURE__ */ jsx21(Typography9, { variant: "subtitle2", sx: { mb: 1 }, children: titulo }),
3865
+ /* @__PURE__ */ jsx21(List3, { dense: true, "data-testid": idDeLista, sx: { maxHeight: 360, overflow: "auto", border: 1, borderColor: "divider", borderRadius: 1 }, children: nombres.map((nombre) => /* @__PURE__ */ jsx21(ListItemButton3, { onClick: () => alternarColumna(nombre), children: /* @__PURE__ */ jsx21(ListItemText2, { primary: nombre }) }, nombre)) })
3866
+ ] });
3867
+ return /* @__PURE__ */ jsxs16(Dialog4, { open, onClose, maxWidth: "sm", fullWidth: true, children: [
3868
+ /* @__PURE__ */ jsx21(DialogTitle4, { children: "Ocultar o mostrar columnas" }),
3869
+ /* @__PURE__ */ jsx21(DialogContent4, { children: /* @__PURE__ */ jsxs16(Box11, { sx: { display: "flex", gap: 2, pt: 1 }, children: [
3870
+ lista("Columnas mostradas", columnas.filter((nombre) => !ocultas.has(nombre)), "columnas-mostradas"),
3871
+ lista("Columnas ocultas", columnas.filter((nombre) => ocultas.has(nombre)), "columnas-ocultas")
3872
+ ] }) }),
3873
+ /* @__PURE__ */ jsx21(DialogActions4, { children: /* @__PURE__ */ jsx21(Button6, { onClick: onClose, children: "Cerrar" }) })
3874
+ ] });
3875
+ };
3876
+
3802
3877
  // src/pages/VerticalEditorPage.tsx
3803
3878
  import { useState as useState15, useCallback as useCallback11, useMemo as useMemo9, useRef as useRef6 } from "react";
3804
3879
  import {
3805
- DialogTitle as DialogTitle4,
3806
- DialogContent as DialogContent4,
3807
- DialogActions as DialogActions4,
3880
+ DialogTitle as DialogTitle5,
3881
+ DialogContent as DialogContent5,
3882
+ DialogActions as DialogActions5,
3808
3883
  Button as Button7,
3809
3884
  Alert,
3810
- Box as Box11,
3885
+ Box as Box12,
3811
3886
  CircularProgress as CircularProgress3,
3812
- IconButton as IconButton5,
3813
- Typography as Typography9
3887
+ IconButton as IconButton6,
3888
+ Typography as Typography10
3814
3889
  } from "@mui/material";
3815
3890
  import CloseIcon from "@mui/icons-material/Close";
3816
- import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3891
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
3892
+ var SIN_OCULTAS = /* @__PURE__ */ new Set();
3817
3893
  var VerticalEditorPage = ({
3818
3894
  tableName,
3819
3895
  tableDefinition,
3820
3896
  initialData,
3821
3897
  fixedFields,
3898
+ columnasOcultas = SIN_OCULTAS,
3822
3899
  isNewRow = false,
3823
3900
  onSaveSuccess,
3824
3901
  onClose
@@ -3940,21 +4017,21 @@ var VerticalEditorPage = ({
3940
4017
  tableDefinition,
3941
4018
  onSaveSuccess
3942
4019
  ]);
3943
- return /* @__PURE__ */ jsxs16(Box11, { sx: { p: 2, display: "flex", flexDirection: "column", height: "100%" }, children: [
3944
- /* @__PURE__ */ jsxs16(DialogTitle4, { sx: { px: 0, pt: 0, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
3945
- /* @__PURE__ */ jsxs16(Typography9, { component: "div", variant: "h6", children: [
4020
+ return /* @__PURE__ */ jsxs17(Box12, { sx: { p: 2, display: "flex", flexDirection: "column", height: "100%" }, children: [
4021
+ /* @__PURE__ */ jsxs17(DialogTitle5, { sx: { px: 0, pt: 0, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
4022
+ /* @__PURE__ */ jsxs17(Typography10, { component: "div", variant: "h6", children: [
3946
4023
  isNewRow ? "Nuevo Registro" : "Editar Registro",
3947
4024
  " - ",
3948
4025
  tableDefinition.title || tableName
3949
4026
  ] }),
3950
- /* @__PURE__ */ jsx21(IconButton5, { onClick: onClose, size: "small", edge: "end", children: /* @__PURE__ */ jsx21(CloseIcon, {}) })
4027
+ /* @__PURE__ */ jsx22(IconButton6, { onClick: onClose, size: "small", edge: "end", children: /* @__PURE__ */ jsx22(CloseIcon, {}) })
3951
4028
  ] }),
3952
- /* @__PURE__ */ jsxs16(DialogContent4, { dividers: true, sx: { px: 2, flexGrow: 1, py: 1 }, children: [
3953
- globalError && /* @__PURE__ */ jsx21(Alert, { severity: "error", sx: { mb: 2 }, children: globalError }),
3954
- /* @__PURE__ */ jsx21(Box11, { sx: { display: "flex", flexDirection: "column", gap: 0.5 }, children: tableDefinition.fields.map((field) => {
4029
+ /* @__PURE__ */ jsxs17(DialogContent5, { dividers: true, sx: { px: 2, flexGrow: 1, py: 1 }, children: [
4030
+ globalError && /* @__PURE__ */ jsx22(Alert, { severity: "error", sx: { mb: 2 }, children: globalError }),
4031
+ /* @__PURE__ */ jsx22(Box12, { sx: { display: "flex", flexDirection: "column", gap: 0.5 }, children: tableDefinition.fields.filter((field) => campoDibujable(field) && !columnasOcultas.has(field.name)).map((field) => {
3955
4032
  const isFixed = fixedFields?.some((ff) => ff.fieldName === field.name);
3956
4033
  const isMandatory = field.nullable === false || tableDefinition.primaryKey.includes(field.name);
3957
- return /* @__PURE__ */ jsx21(Box11, { sx: { width: "100%", maxWidth: "100%" }, children: /* @__PURE__ */ jsx21(
4034
+ return /* @__PURE__ */ jsx22(Box12, { sx: { width: "100%", maxWidth: "100%" }, children: /* @__PURE__ */ jsx22(
3958
4035
  TypedField,
3959
4036
  {
3960
4037
  fieldDef: field,
@@ -3968,12 +4045,12 @@ var VerticalEditorPage = ({
3968
4045
  ) }, field.name);
3969
4046
  }) })
3970
4047
  ] }),
3971
- /* @__PURE__ */ jsxs16(DialogActions4, { sx: { px: 0, pb: 0, pt: 2, justifyContent: "space-between", alignItems: "center" }, children: [
3972
- /* @__PURE__ */ jsxs16(Box11, { children: [
3973
- isSaving && /* @__PURE__ */ jsx21(CircularProgress3, { size: 20, sx: { ml: 2 } }),
3974
- /* @__PURE__ */ jsx21("span", { style: { marginLeft: 10, fontSize: "0.8rem", color: "#666" }, children: isSaving ? "Guardando..." : "Cambios auto-guardados" })
4048
+ /* @__PURE__ */ jsxs17(DialogActions5, { sx: { px: 0, pb: 0, pt: 2, justifyContent: "space-between", alignItems: "center" }, children: [
4049
+ /* @__PURE__ */ jsxs17(Box12, { children: [
4050
+ isSaving && /* @__PURE__ */ jsx22(CircularProgress3, { size: 20, sx: { ml: 2 } }),
4051
+ /* @__PURE__ */ jsx22("span", { style: { marginLeft: 10, fontSize: "0.8rem", color: "#666" }, children: isSaving ? "Guardando..." : "Cambios auto-guardados" })
3975
4052
  ] }),
3976
- /* @__PURE__ */ jsx21(Button7, { variant: "outlined", onClick: onClose, children: "Cerrar" })
4053
+ /* @__PURE__ */ jsx22(Button7, { variant: "outlined", onClick: onClose, children: "Cerrar" })
3977
4054
  ] })
3978
4055
  ] });
3979
4056
  };
@@ -4050,7 +4127,7 @@ var baseDelBundler = import.meta.env?.BASE_URL ?? "/";
4050
4127
  var rutaBase = baseDelBundler.replace(/\/+$/, "");
4051
4128
 
4052
4129
  // src/components/grid/GenericDataGrid.tsx
4053
- import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
4130
+ import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
4054
4131
  var SUMMARY_ROW_HEIGHT = 35;
4055
4132
  var FILTER_ROW_HEIGHT = 35;
4056
4133
  var HEADER_ROW_HEIGHT = 30;
@@ -4061,6 +4138,7 @@ var ALTO_TITULO_ANIDADA = 26;
4061
4138
  var ALTO_MINIMO_CUERPO_ANIDADA = 120;
4062
4139
  var HOLGURA_ANIDADA = 24;
4063
4140
  var ANCHO_MINIMO_ANIDADA = 240;
4141
+ var ANCHO_RESERVADO_PANTALLA = 320;
4064
4142
  var SANGRIA_DETALLE = 28 + 8 + 3;
4065
4143
  var MARGEN_INFERIOR_PAGINA = 24;
4066
4144
  var PRIMERAS_FILAS_ANIDADA = 20;
@@ -4082,8 +4160,8 @@ var StaticAncestorGrid = ({ ancestor, index, fieldsToHide }) => {
4082
4160
  name: field.label || cambiarGuionesBajosPorEspacios(field.name),
4083
4161
  width: 150,
4084
4162
  editable: false,
4085
- renderHeaderCell: () => /* @__PURE__ */ jsx22(Box12, { sx: { p: 0.5, height: "100%", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx22(
4086
- Typography10,
4163
+ renderHeaderCell: () => /* @__PURE__ */ jsx23(Box13, { sx: { p: 0.5, height: "100%", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx23(
4164
+ Typography11,
4087
4165
  {
4088
4166
  variant: "caption",
4089
4167
  sx: {
@@ -4094,11 +4172,11 @@ var StaticAncestorGrid = ({ ancestor, index, fieldsToHide }) => {
4094
4172
  children: field.label || cambiarGuionesBajosPorEspacios(field.name)
4095
4173
  }
4096
4174
  ) }),
4097
- renderCell: (props) => /* @__PURE__ */ jsx22(Box12, { sx: { px: 1, height: "100%", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx22(Typography10, { variant: "body2", sx: { fontSize: "0.875rem" }, noWrap: true, children: String(props.row[field.name] ?? "") }) })
4175
+ renderCell: (props) => /* @__PURE__ */ jsx23(Box13, { sx: { px: 1, height: "100%", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx23(Typography11, { variant: "body2", sx: { fontSize: "0.875rem" }, noWrap: true, children: String(props.row[field.name] ?? "") }) })
4098
4176
  };
4099
4177
  });
4100
4178
  }, [tableDefinition, fieldsToHide]);
4101
- return /* @__PURE__ */ jsxs17(Box12, { sx: {
4179
+ return /* @__PURE__ */ jsxs18(Box13, { sx: {
4102
4180
  mb: 1.5,
4103
4181
  ml: index * 4,
4104
4182
  // El primer ancestro (index 0) no tiene margen, los siguientes sí
@@ -4109,7 +4187,7 @@ var StaticAncestorGrid = ({ ancestor, index, fieldsToHide }) => {
4109
4187
  boxShadow: theme.shadows[1],
4110
4188
  bgcolor: "background.paper"
4111
4189
  }, children: [
4112
- /* @__PURE__ */ jsx22(Box12, { sx: {
4190
+ /* @__PURE__ */ jsx23(Box13, { sx: {
4113
4191
  bgcolor: "primary.main",
4114
4192
  color: "primary.contrastText",
4115
4193
  px: 1.5,
@@ -4120,8 +4198,8 @@ var StaticAncestorGrid = ({ ancestor, index, fieldsToHide }) => {
4120
4198
  alignItems: "center",
4121
4199
  fontWeight: "bold",
4122
4200
  gap: 1
4123
- }, children: /* @__PURE__ */ jsx22(Typography10, { variant: "subtitle2", component: "div", children: cambiarGuionesBajosPorEspacios(ancestor.tableName) }) }),
4124
- /* @__PURE__ */ jsx22(
4201
+ }, children: /* @__PURE__ */ jsx23(Typography11, { variant: "subtitle2", component: "div", children: cambiarGuionesBajosPorEspacios(ancestor.tableName) }) }),
4202
+ /* @__PURE__ */ jsx23(
4125
4203
  DataGrid,
4126
4204
  {
4127
4205
  className: "rdg-light",
@@ -4171,6 +4249,12 @@ var GenericDataGrid = ({
4171
4249
  const [cargandoInicial, setCargandoInicial] = useState16(!guardada);
4172
4250
  const [errorDeCarga, setErrorDeCarga] = useState16(null);
4173
4251
  const [anchosCalculados, setAnchosCalculados] = useState16(() => guardada?.anchosCalculados ?? /* @__PURE__ */ new Map());
4252
+ const [columnasOcultas, setColumnasOcultas] = useState16(() => guardada?.columnasOcultas ?? []);
4253
+ const [openDialogoDeColumnas, setOpenDialogoDeColumnas] = useState16(false);
4254
+ const conjuntoDeOcultas = useMemo10(() => new Set(columnasOcultas), [columnasOcultas]);
4255
+ const ocultarColumna = useCallback12((columna) => setColumnasOcultas((anteriores) => anteriores.includes(columna) ? anteriores : [...anteriores, columna]), []);
4256
+ const alternarColumna = useCallback12((columna) => setColumnasOcultas((anteriores) => anteriores.includes(columna) ? anteriores.filter((oculta) => oculta !== columna) : [...anteriores, columna]), []);
4257
+ const abrirDialogoDeColumnas = useCallback12(() => setOpenDialogoDeColumnas(true), []);
4174
4258
  const [detallesAbiertos, setDetallesAbiertos] = useState16(guardada?.detallesAbiertos ?? []);
4175
4259
  const [altosDetalle, setAltosDetalle] = useState16(() => guardada?.altosDetalle ?? /* @__PURE__ */ new Map());
4176
4260
  const [anchosDetalle, setAnchosDetalle] = useState16(() => /* @__PURE__ */ new Map());
@@ -4270,7 +4354,7 @@ var GenericDataGrid = ({
4270
4354
  const firstDisplayCount = tableDefinition?.firstDisplayCount ?? PRIMERAS_FILAS_ANIDADA;
4271
4355
  const firstDisplayOverLimit = tableDefinition?.firstDisplayOverLimit ?? LIMITE_PRIMERAS_FILAS_ANIDADA;
4272
4356
  const filasVisiblesMax = anidada ? filteredRows.length <= firstDisplayOverLimit ? filteredRows.length : firstDisplayCount : void 0;
4273
- const conPie = !anidada || !!tableDefinition?.fields.some((field) => field.aggregate);
4357
+ const conPie = !!tableDefinition?.fields.some((field) => field.aggregate);
4274
4358
  const { gridHeight, bottomSummaryRow, summaryRows, filterHeight } = useGridLayout({
4275
4359
  filteredRows,
4276
4360
  isFilterRowVisible,
@@ -4364,7 +4448,8 @@ var GenericDataGrid = ({
4364
4448
  const definition = parametros.tableDef ? fusionar(definicionDelServidor, parametros.tableDef) : definicionDelServidor;
4365
4449
  setTableDefinition(definition);
4366
4450
  setSortColumns(getInitialSortColumns(definition));
4367
- setAnchosCalculados(calcularAnchosDeColumnas(definition, data));
4451
+ setAnchosCalculados(calcularAnchosDeColumnas(definition, data, window.innerWidth - ANCHO_RESERVADO_PANTALLA));
4452
+ setColumnasOcultas(columnasOcultasIniciales(definition));
4368
4453
  setTableData(data);
4369
4454
  const filasDeFiltros = parametros.filterColumns ?? aFiltrosIniciales(definition.filterColumns);
4370
4455
  if (filasDeFiltros.length > 1) console.warn(`'${tableName}': varias filas de filtros (alternativas) todav\xEDa no est\xE1n soportadas; se aplica la primera`);
@@ -4387,8 +4472,8 @@ var GenericDataGrid = ({
4387
4472
  }, [tableName, claveFixedFields, fixedFields, claveParametros, parametros, showError, callApi, getInitialSortColumns, setSortColumns, aplicarFiltrosIniciales, guardada]);
4388
4473
  useEffect6(() => {
4389
4474
  if (!claveCache || !tableDefinition || cargandoInicial) return;
4390
- cache.set(claveCache, { tableDefinition, tableData, anchosCalculados, sortColumns, detallesAbiertos, altosDetalle });
4391
- }, [cache, claveCache, tableDefinition, tableData, anchosCalculados, sortColumns, detallesAbiertos, altosDetalle, cargandoInicial]);
4475
+ cache.set(claveCache, { tableDefinition, tableData, anchosCalculados, columnasOcultas, sortColumns, detallesAbiertos, altosDetalle });
4476
+ }, [cache, claveCache, tableDefinition, tableData, anchosCalculados, columnasOcultas, sortColumns, detallesAbiertos, altosDetalle, cargandoInicial]);
4392
4477
  useEffect6(() => {
4393
4478
  let timer = null;
4394
4479
  const desuscribir = estadoGrilla.suscribir(() => {
@@ -4427,7 +4512,8 @@ var GenericDataGrid = ({
4427
4512
  callApi,
4428
4513
  resetSorting: tableDefinition ? resetSortingDeLaTabla : void 0,
4429
4514
  triggerImport: abrirImportacion,
4430
- triggerExport: abrirExportacion
4515
+ triggerExport: abrirExportacion,
4516
+ abrirColumnas: abrirDialogoDeColumnas
4431
4517
  });
4432
4518
  const abrirOpcionesDeGrilla = useCallback12((anchorEl) => {
4433
4519
  setDataGridOptionsAnchorEl(anchorEl);
@@ -4441,6 +4527,8 @@ var GenericDataGrid = ({
4441
4527
  tableDefinition,
4442
4528
  anchosCalculados,
4443
4529
  fixedFields,
4530
+ columnasOcultas: conjuntoDeOcultas,
4531
+ ocultarColumna,
4444
4532
  primaryKey,
4445
4533
  setTableData,
4446
4534
  handleKeyPressInEditor,
@@ -4514,13 +4602,13 @@ var GenericDataGrid = ({
4514
4602
  }, [altoMaximoDetalleEfectivo]);
4515
4603
  const renderDetalle = useCallback12((fila) => {
4516
4604
  const { detailTable } = fila;
4517
- return /* @__PURE__ */ jsx22(
4605
+ return /* @__PURE__ */ jsx23(
4518
4606
  "div",
4519
4607
  {
4520
4608
  className: "fpr-detalle",
4521
4609
  style: { maxWidth: anchoVisible ? anchoVisible - 2 : void 0 },
4522
4610
  onKeyDown: (e) => e.stopPropagation(),
4523
- children: detailTable.table ? /* @__PURE__ */ jsx22(
4611
+ children: detailTable.table ? /* @__PURE__ */ jsx23(
4524
4612
  GenericDataGrid,
4525
4613
  {
4526
4614
  tableName: detailTable.table,
@@ -4533,7 +4621,7 @@ var GenericDataGrid = ({
4533
4621
  onOpenDetail,
4534
4622
  ancestors: tableDefinition ? [...ancestors, { tableName: tableDefinition.name, row: fila.filaPadre, tableDefinition, fixedFields: fila.fixedFields }] : ancestors
4535
4623
  }
4536
- ) : detailTable.wScreen ? /* @__PURE__ */ jsx22(
4624
+ ) : detailTable.wScreen ? /* @__PURE__ */ jsx23(
4537
4625
  DetalleWScreen,
4538
4626
  {
4539
4627
  fila,
@@ -4541,7 +4629,7 @@ var GenericDataGrid = ({
4541
4629
  onAlto: (alto) => cambiarAltoDetalle(fila.clave, alto),
4542
4630
  onAncho: (ancho) => cambiarAnchoDetalle(fila.clave, ancho)
4543
4631
  }
4544
- ) : /* @__PURE__ */ jsxs17(Alert2, { severity: "warning", children: [
4632
+ ) : /* @__PURE__ */ jsxs18(Alert2, { severity: "warning", children: [
4545
4633
  "El detalle '",
4546
4634
  detailTable.abr,
4547
4635
  "' no tiene ni tabla ni wScreen."
@@ -4561,7 +4649,7 @@ var GenericDataGrid = ({
4561
4649
  [exitingRowIds, primaryKey]
4562
4650
  );
4563
4651
  const anchoContenido = useMemo10(() => {
4564
- if (!anidada || columns.length === 0) return void 0;
4652
+ if (columns.length === 0) return void 0;
4565
4653
  const sumaColumnas = columns.reduce((total, col) => {
4566
4654
  const medido = columnWidths.get(col.key)?.width;
4567
4655
  return total + (typeof medido === "number" ? medido : typeof col.width === "number" ? col.width : 100);
@@ -4571,8 +4659,9 @@ var GenericDataGrid = ({
4571
4659
  return Math.max(ANCHO_MINIMO_ANIDADA, propio, anchoDetalleMax > 0 ? anchoDetalleMax + SANGRIA_DETALLE + (medidas?.barra ?? 0) + 2 : 0);
4572
4660
  }, [anidada, columns, columnWidths, medidas?.barra, detallesAbiertos, anchosDetalle]);
4573
4661
  useEffect6(() => {
4574
- if (anchoContenido !== void 0) onAnchoDeseado?.(anchoContenido);
4575
- }, [onAnchoDeseado, anchoContenido]);
4662
+ if (anidada && anchoContenido !== void 0) onAnchoDeseado?.(anchoContenido);
4663
+ }, [onAnchoDeseado, anidada, anchoContenido]);
4664
+ const anchoExterior = anchoContenido === void 0 ? void 0 : anidada ? anchoContenido : anchoContenido - 2 + 32 + ancestors.length * 32;
4576
4665
  const estiloGrilla = useMemo10(() => ({ height: "100%", width: "100%", boxSizing: "border-box", ...gridStyles }), [gridStyles]);
4577
4666
  const bottomSummaryRows = useMemo10(() => conPie ? [bottomSummaryRow] : [], [conPie, bottomSummaryRow]);
4578
4667
  const vistaGrilla = useMemo10(() => ({ isFilterRowVisible }), [isFilterRowVisible]);
@@ -4587,19 +4676,19 @@ var GenericDataGrid = ({
4587
4676
  };
4588
4677
  const fallBackMessage = getFallBackMessage();
4589
4678
  if (cargandoInicial) {
4590
- return /* @__PURE__ */ jsxs17(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: [
4591
- /* @__PURE__ */ jsx22(CircularProgress4, {}),
4592
- /* @__PURE__ */ jsx22(Typography10, { variant: "h6", sx: { ml: 2 }, children: "Cargando tabla..." })
4679
+ return /* @__PURE__ */ jsxs18(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: [
4680
+ /* @__PURE__ */ jsx23(CircularProgress4, {}),
4681
+ /* @__PURE__ */ jsx23(Typography11, { variant: "h6", sx: { ml: 2 }, children: "Cargando tabla..." })
4593
4682
  ] });
4594
4683
  }
4595
4684
  if (errorDeCarga) {
4596
- return /* @__PURE__ */ jsx22(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: /* @__PURE__ */ jsx22(Alert2, { severity: "error", children: errorDeCarga }) });
4685
+ return /* @__PURE__ */ jsx23(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: /* @__PURE__ */ jsx23(Alert2, { severity: "error", children: errorDeCarga }) });
4597
4686
  }
4598
4687
  if (!tableDefinition) {
4599
- return /* @__PURE__ */ jsx22(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: /* @__PURE__ */ jsx22(Alert2, { severity: "warning", children: "No se pudo cargar la definici\xF3n de la tabla." }) });
4688
+ return /* @__PURE__ */ jsx23(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: /* @__PURE__ */ jsx23(Alert2, { severity: "warning", children: "No se pudo cargar la definici\xF3n de la tabla." }) });
4600
4689
  }
4601
- return /* @__PURE__ */ jsx22(CacheGrillasContext.Provider, { value: cache, children: /* @__PURE__ */ jsxs17(Box12, { sx: { pt: anidada ? 0 : 2, display: "flex", flexDirection: "column", height: "100%", boxSizing: "border-box", width: anchoContenido ?? void 0, maxWidth: "100%" }, children: [
4602
- !anidada && ancestors.length > 0 && /* @__PURE__ */ jsx22(Box12, { sx: { px: 2, pt: 0, pb: 1, display: "flex", flexDirection: "column" }, children: ancestors.map((ancestor, index) => /* @__PURE__ */ jsx22(
4690
+ return /* @__PURE__ */ jsx23(CacheGrillasContext.Provider, { value: cache, children: /* @__PURE__ */ jsxs18(Box13, { sx: { pt: anidada ? 0 : 2, display: "flex", flexDirection: "column", height: "100%", boxSizing: "border-box", width: anchoExterior, maxWidth: "100%" }, children: [
4691
+ !anidada && ancestors.length > 0 && /* @__PURE__ */ jsx23(Box13, { sx: { px: 2, pt: 0, pb: 1, display: "flex", flexDirection: "column" }, children: ancestors.map((ancestor, index) => /* @__PURE__ */ jsx23(
4603
4692
  StaticAncestorGrid,
4604
4693
  {
4605
4694
  ancestor,
@@ -4608,8 +4697,8 @@ var GenericDataGrid = ({
4608
4697
  },
4609
4698
  index
4610
4699
  )) }),
4611
- /* @__PURE__ */ jsxs17(
4612
- Box12,
4700
+ /* @__PURE__ */ jsxs18(
4701
+ Box13,
4613
4702
  {
4614
4703
  sx: {
4615
4704
  backgroundColor: theme.palette.primary.main,
@@ -4631,17 +4720,17 @@ var GenericDataGrid = ({
4631
4720
  gap: 1
4632
4721
  },
4633
4722
  children: [
4634
- /* @__PURE__ */ jsxs17(Typography10, { variant: "subtitle2", component: "div", children: [
4723
+ /* @__PURE__ */ jsxs18(Typography11, { variant: "subtitle2", component: "div", children: [
4635
4724
  titulo ?? cambiarGuionesBajosPorEspacios(tableDefinition.title || tableDefinition.name),
4636
4725
  " - ",
4637
4726
  filteredRows.length === tableData.length ? `${tableData.length} registros` : `${filteredRows.length} (F)`
4638
4727
  ] }),
4639
- loading && /* @__PURE__ */ jsx22(CircularProgress4, { size: 14, color: "inherit" })
4728
+ loading && /* @__PURE__ */ jsx23(CircularProgress4, { size: 14, color: "inherit" })
4640
4729
  ]
4641
4730
  }
4642
4731
  ),
4643
- /* @__PURE__ */ jsxs17(
4644
- Box12,
4732
+ /* @__PURE__ */ jsxs18(
4733
+ Box13,
4645
4734
  {
4646
4735
  sx: {
4647
4736
  // Una grilla anidada se acota con px (el alto que le dio la de arriba, o el de una wScreen): una wScreen
@@ -4659,7 +4748,7 @@ var GenericDataGrid = ({
4659
4748
  pb: anidada ? 0 : 3
4660
4749
  },
4661
4750
  children: [
4662
- /* @__PURE__ */ jsx22(EstadoGrillaContext.Provider, { value: estadoGrilla, children: /* @__PURE__ */ jsx22(VistaGrillaContext.Provider, { value: vistaGrilla, children: /* @__PURE__ */ jsx22(DetallesGrillaContext.Provider, { value: detallesGrilla, children: /* @__PURE__ */ jsx22(
4751
+ /* @__PURE__ */ jsx23(EstadoGrillaContext.Provider, { value: estadoGrilla, children: /* @__PURE__ */ jsx23(VistaGrillaContext.Provider, { value: vistaGrilla, children: /* @__PURE__ */ jsx23(DetallesGrillaContext.Provider, { value: detallesGrilla, children: /* @__PURE__ */ jsx23(
4663
4752
  DataGrid,
4664
4753
  {
4665
4754
  className: "rdg-light",
@@ -4689,13 +4778,13 @@ var GenericDataGrid = ({
4689
4778
  onCellKeyDown: handleCellKeyDown
4690
4779
  }
4691
4780
  ) }) }) }),
4692
- fallBackMessage && /* @__PURE__ */ jsx22(
4693
- Box12,
4781
+ fallBackMessage && /* @__PURE__ */ jsx23(
4782
+ Box13,
4694
4783
  {
4695
4784
  sx: {
4696
4785
  position: "absolute",
4697
4786
  top: filterHeight + HEADER_ROW_HEIGHT + 1,
4698
- bottom: anidada ? (conPie ? SUMMARY_ROW_HEIGHT : 0) + 4 : 102,
4787
+ bottom: anidada ? (conPie ? SUMMARY_ROW_HEIGHT : 0) + 4 : conPie ? 102 : 102 - SUMMARY_ROW_HEIGHT,
4699
4788
  left: 18,
4700
4789
  right: 0,
4701
4790
  display: "flex",
@@ -4706,24 +4795,35 @@ var GenericDataGrid = ({
4706
4795
  zIndex: tableData.length === 0 ? 10 : 1
4707
4796
  // Mantenemos el zIndex superior para el estado vacío total
4708
4797
  },
4709
- children: /* @__PURE__ */ jsx22(Typography10, { variant: "body2", color: "textSecondary", children: fallBackMessage })
4798
+ children: /* @__PURE__ */ jsx23(Typography11, { variant: "body2", color: "textSecondary", children: fallBackMessage })
4710
4799
  }
4711
4800
  )
4712
4801
  ]
4713
4802
  }
4714
4803
  ),
4715
- /* @__PURE__ */ jsx22(
4804
+ /* @__PURE__ */ jsx23(
4716
4805
  ExportDialog,
4717
4806
  {
4718
4807
  open: openExportDialog,
4719
4808
  onClose: () => setOpenExportDialog(false),
4720
4809
  tableDefinition,
4810
+ columnasOcultas: conjuntoDeOcultas,
4721
4811
  tableData,
4722
4812
  filteredRows,
4723
4813
  username: usuario || "anonymous"
4724
4814
  }
4725
4815
  ),
4726
- /* @__PURE__ */ jsx22(
4816
+ /* @__PURE__ */ jsx23(
4817
+ DialogoDeColumnas,
4818
+ {
4819
+ open: openDialogoDeColumnas,
4820
+ onClose: () => setOpenDialogoDeColumnas(false),
4821
+ columnas: (tableDefinition?.fields ?? []).filter(campoDibujable).map((field) => field.name).filter((nombre) => !columnaOcultaPorFixedField(fixedFields, nombre)),
4822
+ ocultas: conjuntoDeOcultas,
4823
+ alternarColumna
4824
+ }
4825
+ ),
4826
+ /* @__PURE__ */ jsx23(
4727
4827
  ConfirmDialog,
4728
4828
  {
4729
4829
  open: openConfirmDialog,
@@ -4732,7 +4832,7 @@ var GenericDataGrid = ({
4732
4832
  message: rowToDelete && rowToDelete[NEW_ROW_INDICATOR] ? `\xBFEst\xE1s seguro de que quieres eliminar esta nueva fila (ID: ${getPrimaryKeyValues(rowToDelete, primaryKey)}) localmente?` : rowToDelete ? `\xBFEst\xE1s seguro de que quieres eliminar la fila con ID: ${getPrimaryKeyValues(rowToDelete, primaryKey)} de la base de datos? Esta acci\xF3n es irreversible.` : "\xBFEst\xE1s seguro de que quieres eliminar este registro? Esta acci\xF3n es irreversible."
4733
4833
  }
4734
4834
  ),
4735
- /* @__PURE__ */ jsx22(
4835
+ /* @__PURE__ */ jsx23(
4736
4836
  DataGridOptionsDialog,
4737
4837
  {
4738
4838
  open: openDataGridOptions,
@@ -4741,7 +4841,7 @@ var GenericDataGrid = ({
4741
4841
  anchorEl: dataGridOptionsAnchorEl
4742
4842
  }
4743
4843
  ),
4744
- /* @__PURE__ */ jsx22(
4844
+ /* @__PURE__ */ jsx23(
4745
4845
  ImportDialog,
4746
4846
  {
4747
4847
  open: openImportDialog,
@@ -4750,20 +4850,21 @@ var GenericDataGrid = ({
4750
4850
  loading
4751
4851
  }
4752
4852
  ),
4753
- /* @__PURE__ */ jsx22(
4754
- Dialog4,
4853
+ /* @__PURE__ */ jsx23(
4854
+ Dialog5,
4755
4855
  {
4756
4856
  open: openVerticalEditDialog,
4757
4857
  onClose: () => setOpenVerticalEditDialog(false),
4758
4858
  maxWidth: "sm",
4759
4859
  fullWidth: true,
4760
- children: rowToEditVertical && /* @__PURE__ */ jsx22(
4860
+ children: rowToEditVertical && /* @__PURE__ */ jsx23(
4761
4861
  VerticalEditorPage,
4762
4862
  {
4763
4863
  tableName,
4764
4864
  tableDefinition,
4765
4865
  initialData: rowToEditVertical,
4766
4866
  fixedFields,
4867
+ columnasOcultas: conjuntoDeOcultas,
4767
4868
  isNewRow: !!rowToEditVertical[NEW_ROW_INDICATOR],
4768
4869
  onSaveSuccess: (savedRow, isNewItem) => {
4769
4870
  setRowToEditVertical(savedRow);
@@ -4802,27 +4903,27 @@ var GenericDataGrid = ({
4802
4903
  )
4803
4904
  }
4804
4905
  ),
4805
- contenidoDeCelda && /* @__PURE__ */ jsx22(VentanaDeTexto, { anclaje: contenidoDeCelda.anclaje, texto: contenidoDeCelda.texto, editable: false, onAceptar: cerrarContenidoDeCelda, onCerrar: cerrarContenidoDeCelda })
4906
+ contenidoDeCelda && /* @__PURE__ */ jsx23(VentanaDeTexto, { anclaje: contenidoDeCelda.anclaje, texto: contenidoDeCelda.texto, editable: false, onAceptar: cerrarContenidoDeCelda, onCerrar: cerrarContenidoDeCelda })
4806
4907
  ] }) });
4807
4908
  };
4808
4909
  var GenericDataGrid_default = GenericDataGrid;
4809
4910
 
4810
4911
  // src/App.tsx
4811
- import React33, { useEffect as useEffect18, useMemo as useMemo16 } from "react";
4812
- import { BrowserRouter, Routes, Route, useLocation as useLocation8, Navigate as Navigate4, Outlet as Outlet2 } from "react-router-dom";
4912
+ import React34, { useEffect as useEffect18, useMemo as useMemo17 } from "react";
4913
+ import { BrowserRouter, Routes, Route, useLocation as useLocation9, Navigate as Navigate4, Outlet as Outlet2 } from "react-router-dom";
4813
4914
 
4814
4915
  // src/components/MainLayout.tsx
4815
4916
  import { useEffect as useEffect9 } from "react";
4816
4917
  import { Outlet } from "react-router-dom";
4817
4918
  import {
4818
- Box as Box14,
4919
+ Box as Box15,
4819
4920
  AppBar,
4820
4921
  Toolbar,
4821
- Typography as Typography12,
4922
+ Typography as Typography13,
4822
4923
  Button as Button8,
4823
4924
  Drawer,
4824
4925
  CssBaseline,
4825
- IconButton as IconButton6,
4926
+ IconButton as IconButton8,
4826
4927
  useTheme as useTheme5,
4827
4928
  useMediaQuery
4828
4929
  } from "@mui/material";
@@ -4831,7 +4932,16 @@ import {
4831
4932
  import { createContext as createContext4, useContext as useContext5, useState as useState17, useEffect as useEffect7, useCallback as useCallback13, useMemo as useMemo11, useRef as useRef8 } from "react";
4832
4933
  import { useLocation, useNavigate } from "react-router-dom";
4833
4934
  import { useSelector as useSelector2, useDispatch as useDispatch2 } from "react-redux";
4834
- import { jsx as jsx23 } from "react/jsx-runtime";
4935
+
4936
+ // src/utils/cierreVoluntario.ts
4937
+ var cierreVoluntario = false;
4938
+ var marcarCierreVoluntario = (activo) => {
4939
+ cierreVoluntario = activo;
4940
+ };
4941
+ var huboCierreVoluntario = () => cierreVoluntario;
4942
+
4943
+ // src/contexts/AppContext.tsx
4944
+ import { jsx as jsx24 } from "react/jsx-runtime";
4835
4945
  var AuthContext = createContext4(void 0);
4836
4946
  var useApp = () => {
4837
4947
  const context = useContext5(AuthContext);
@@ -4847,9 +4957,10 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4847
4957
  const [showSessionExpiredMessage, setShowSessionExpiredMessage] = useState17(false);
4848
4958
  const isRedirectingRef = useRef8(false);
4849
4959
  const huboSesionRef = useRef8(false);
4960
+ const cierreVoluntarioRef = useRef8(false);
4850
4961
  const dispatch = useDispatch2();
4851
4962
  const clientContext = useSelector2((state) => state.clientContext);
4852
- if (clientContext.status === "succeeded") huboSesionRef.current = true;
4963
+ if (clientContext.status === "succeeded" && !cierreVoluntarioRef.current) huboSesionRef.current = true;
4853
4964
  const navigate = useNavigate();
4854
4965
  const handleExpiredSessionRedirect = useCallback13(() => {
4855
4966
  isRedirectingRef.current = true;
@@ -4860,6 +4971,17 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4860
4971
  isRedirectingRef.current = false;
4861
4972
  }, 2e3);
4862
4973
  }, [navigate]);
4974
+ const avisarCierreVoluntario = useCallback13((activo) => {
4975
+ cierreVoluntarioRef.current = activo;
4976
+ marcarCierreVoluntario(activo);
4977
+ if (activo) huboSesionRef.current = false;
4978
+ }, []);
4979
+ const limpiarContextoPorSesionPerdida = useCallback13(() => {
4980
+ if (clientContext.status === "succeeded" && clientContext.username !== "" && !cierreVoluntarioRef.current) {
4981
+ dispatch(setRedirectUsername(clientContext.username));
4982
+ }
4983
+ dispatch(clearClientContext());
4984
+ }, [dispatch, clientContext.status, clientContext.username]);
4863
4985
  const location = useLocation();
4864
4986
  const isCurrentPathPublic = useCallback13(() => {
4865
4987
  const current = location.pathname;
@@ -4885,8 +5007,8 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4885
5007
  }
4886
5008
  } else {
4887
5009
  setIsLoggedIn(false);
4888
- dispatch(clearClientContext());
4889
- if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current) {
5010
+ limpiarContextoPorSesionPerdida();
5011
+ if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current && !cierreVoluntarioRef.current) {
4890
5012
  console.warn("Sesi\xF3n inv\xE1lida en ruta protegida. Mostrando modal.");
4891
5013
  setShowSessionExpiredMessage(true);
4892
5014
  } else {
@@ -4896,13 +5018,13 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4896
5018
  } catch (error) {
4897
5019
  console.error("Error checking session:", error);
4898
5020
  setIsLoggedIn(false);
4899
- dispatch(clearClientContext());
5021
+ limpiarContextoPorSesionPerdida();
4900
5022
  const isPublic = publicPaths.some((p) => location.pathname === p || location.pathname.startsWith(p + "/"));
4901
- if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current) {
5023
+ if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current && !cierreVoluntarioRef.current) {
4902
5024
  setShowSessionExpiredMessage(true);
4903
5025
  }
4904
5026
  }
4905
- }, [dispatch, clientContext.status, showSessionExpiredMessage, isCurrentPathPublic]);
5027
+ }, [dispatch, clientContext.status, showSessionExpiredMessage, isCurrentPathPublic, limpiarContextoPorSesionPerdida]);
4906
5028
  useEffect7(() => {
4907
5029
  checkSession();
4908
5030
  const intervalId = setInterval(checkSession, 15 * 1e3);
@@ -4916,26 +5038,30 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4916
5038
  checkSession,
4917
5039
  showSessionExpiredMessage,
4918
5040
  setShowSessionExpiredMessage,
4919
- handleExpiredSessionRedirect
5041
+ handleExpiredSessionRedirect,
5042
+ avisarCierreVoluntario
4920
5043
  }), [
4921
5044
  isLoggedIn,
4922
5045
  setIsLoggedIn,
4923
5046
  checkSession,
4924
5047
  showSessionExpiredMessage,
4925
5048
  setShowSessionExpiredMessage,
4926
- handleExpiredSessionRedirect
5049
+ handleExpiredSessionRedirect,
5050
+ avisarCierreVoluntario
4927
5051
  ]);
4928
- return /* @__PURE__ */ jsx23(AuthContext.Provider, { value: authContextValue, children });
5052
+ return /* @__PURE__ */ jsx24(AuthContext.Provider, { value: authContextValue, children });
4929
5053
  };
4930
5054
 
4931
5055
  // src/components/MainLayout.tsx
5056
+ import { useNavigate as useNavigate4 } from "react-router-dom";
4932
5057
  import LogoutIcon from "@mui/icons-material/Logout";
5058
+ import VpnKeyIcon from "@mui/icons-material/VpnKey";
4933
5059
  import MenuIcon from "@mui/icons-material/Menu";
4934
5060
  import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
4935
5061
 
4936
5062
  // src/components/SideMenu.tsx
4937
- import React19, { useEffect as useEffect8 } from "react";
4938
- import { List as List3, ListItem, ListItemButton as ListItemButton3, ListItemIcon as ListItemIcon2, ListItemText as ListItemText2, Divider as Divider2, Box as Box13, Typography as Typography11, Collapse } from "@mui/material";
5063
+ import React19, { useEffect as useEffect8, useMemo as useMemo12, useRef as useRef9, useState as useState18 } from "react";
5064
+ import { List as List4, ListItemButton as ListItemButton4, ListItemIcon as ListItemIcon2, ListItemText as ListItemText3, Divider as Divider2, Box as Box14, Typography as Typography12, Collapse, TextField as TextField2, InputAdornment as InputAdornment2, IconButton as IconButton7 } from "@mui/material";
4939
5065
  import ExpandLess from "@mui/icons-material/ExpandLess";
4940
5066
  import ExpandMore from "@mui/icons-material/ExpandMore";
4941
5067
  import TableChartIcon from "@mui/icons-material/TableChart";
@@ -4943,18 +5069,56 @@ import FolderIcon from "@mui/icons-material/Folder";
4943
5069
  import DnsIcon from "@mui/icons-material/Dns";
4944
5070
  import WarningAmberIcon3 from "@mui/icons-material/WarningAmber";
4945
5071
  import DesktopWindowsIcon from "@mui/icons-material/DesktopWindows";
4946
- import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
4947
- import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
5072
+ import SearchIcon3 from "@mui/icons-material/Search";
5073
+ import ClearIcon from "@mui/icons-material/Clear";
4948
5074
  import { useNavigate as useNavigate2, useLocation as useLocation2 } from "react-router-dom";
4949
- import { blue, orange, teal, red, grey } from "@mui/material/colors";
5075
+ import { blue, orange, teal, indigo, red } from "@mui/material/colors";
5076
+
5077
+ // src/puente/habilitacion.ts
5078
+ var habilitado = false;
5079
+ var habilitarPuenteBP = () => {
5080
+ habilitado = true;
5081
+ };
5082
+ var puenteBPHabilitado = () => habilitado;
4950
5083
 
4951
5084
  // src/utils/menuInicial.ts
4952
- function rutaDeItemDeMenu(item) {
5085
+ function rutaDeTablaDeMenu(camino) {
5086
+ return `/menu/${camino.map(encodeURIComponent).join("/")}`;
5087
+ }
5088
+ function gruposDeItem(menu, item) {
5089
+ for (const candidato of menu) {
5090
+ if (candidato === item) return [];
5091
+ if (candidato.menuType === "menu") {
5092
+ const grupos = gruposDeItem(candidato.menuContent, item);
5093
+ if (grupos) return [candidato.name, ...grupos];
5094
+ }
5095
+ }
5096
+ return void 0;
5097
+ }
5098
+ function itemDeCamino(menu, camino) {
5099
+ const [nombre, ...resto] = camino;
5100
+ const item = menu.find((candidato) => candidato.name === nombre && candidato.menuType === (resto.length ? "menu" : "table"));
5101
+ return item && resto.length ? itemDeCamino(item.menuContent, resto) : item;
5102
+ }
5103
+ function primeraTablaConNombre(menu, nombre) {
5104
+ for (const item of menu) {
5105
+ if (item.menuType === "table" && item.name === nombre) return item;
5106
+ const encontrado = item.menuType === "menu" ? primeraTablaConNombre(item.menuContent, nombre) : void 0;
5107
+ if (encontrado) return encontrado;
5108
+ }
5109
+ return void 0;
5110
+ }
5111
+ function itemDeRuta(menu, camino) {
5112
+ if (camino.length === 0) return void 0;
5113
+ const item = itemDeCamino(menu, camino) ?? primeraTablaConNombre(menu, camino[camino.length - 1]);
5114
+ return item;
5115
+ }
5116
+ function rutaDeItemDeMenu(item, grupos = []) {
4953
5117
  if (item.menuType === "table") {
4954
5118
  const consulta = new URLSearchParams();
4955
5119
  if (item.td) consulta.append("td", JSON.stringify(item.td));
4956
5120
  if (item.fc) consulta.append("fc", JSON.stringify(item.fc));
4957
- return `/menu/${item.name}${consulta.size ? `?${consulta.toString()}` : ""}`;
5121
+ return `${rutaDeTablaDeMenu([...grupos, item.name])}${consulta.size ? `?${consulta.toString()}` : ""}`;
4958
5122
  }
4959
5123
  if (item.menuType === "proc") return `/procedures/${item.name}`;
4960
5124
  if (wScreens[item.menuType] !== void 0) return `/wScreens/${item.menuType}`;
@@ -4967,140 +5131,266 @@ function itemInicial(menu) {
4967
5131
  return elegido.menuType === "menu" ? itemInicial(elegido.menuContent) : elegido;
4968
5132
  }
4969
5133
 
5134
+ // src/utils/busquedaDeMenu.ts
5135
+ function etiquetaDeItem(item) {
5136
+ return item.label || item.name;
5137
+ }
5138
+ function claveDeMenu(camino) {
5139
+ return camino.join("/");
5140
+ }
5141
+ function hojasDeMenu(menu, grupos = [], etiquetasDeGrupos = []) {
5142
+ return menu.flatMap(
5143
+ (item) => item.menuType === "menu" ? hojasDeMenu(item.menuContent, [...grupos, item.name], [...etiquetasDeGrupos, etiquetaDeItem(item)]) : [{ item, grupos, etiquetasDeGrupos, clave: claveDeMenu([...grupos, item.name]) }]
5144
+ );
5145
+ }
5146
+ function normalizarParaBuscar(texto) {
5147
+ return texto.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/_/g, " ").toLowerCase();
5148
+ }
5149
+ function buscarEnMenu(hojas, consulta) {
5150
+ const palabras = normalizarParaBuscar(consulta).split(/\s+/).filter((palabra) => palabra !== "");
5151
+ if (palabras.length === 0) return [];
5152
+ const propias = [];
5153
+ const porGrupo = [];
5154
+ for (const hoja of hojas) {
5155
+ const propia = normalizarParaBuscar(`${etiquetaDeItem(hoja.item)} ${hoja.item.name}`);
5156
+ const completa = `${hoja.etiquetasDeGrupos.map(normalizarParaBuscar).join(" ")} ${propia}`;
5157
+ if (!palabras.every((palabra) => completa.includes(palabra))) continue;
5158
+ (palabras.every((palabra) => propia.includes(palabra)) ? propias : porGrupo).push(hoja);
5159
+ }
5160
+ return [...propias, ...porGrupo];
5161
+ }
5162
+
4970
5163
  // src/components/SideMenu.tsx
4971
- import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
4972
- var MenuListItem = ({ item, level, onMenuItemClick }) => {
5164
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
5165
+ var CLAVE_RECIENTES = "fpr.menu.recientes";
5166
+ var MAXIMO_RECIENTES = 4;
5167
+ var getIcon = (item, destacado = false) => {
5168
+ switch (item.menuType) {
5169
+ case "table":
5170
+ return /* @__PURE__ */ jsx25(TableChartIcon, { sx: destacado ? { color: blue[700] } : void 0 });
5171
+ case "proc":
5172
+ return /* @__PURE__ */ jsx25(DnsIcon, { sx: destacado ? { color: teal[700] } : void 0 });
5173
+ case "menu":
5174
+ return /* @__PURE__ */ jsx25(FolderIcon, { sx: destacado ? { color: orange[800] } : void 0 });
5175
+ default:
5176
+ if (wScreens[item.menuType] !== void 0 || puenteBPHabilitado()) {
5177
+ return /* @__PURE__ */ jsx25(DesktopWindowsIcon, { sx: destacado ? { color: indigo[500] } : void 0 });
5178
+ }
5179
+ return /* @__PURE__ */ jsx25(WarningAmberIcon3, { sx: destacado ? { color: red[500] } : void 0 });
5180
+ }
5181
+ };
5182
+ var estaSeleccionado = (item, grupos, currentPath, hash = "") => {
5183
+ const pathParts = currentPath.split("/");
5184
+ const urlTableName = pathParts[1] === "table" ? pathParts[2] : null;
5185
+ if (item.menuType === "table") {
5186
+ return currentPath === rutaDeTablaDeMenu([...grupos, item.name]) || urlTableName === (item.table || item.name);
5187
+ } else if (item.menuType === "proc") {
5188
+ return currentPath === `/procedures/${item.name}`;
5189
+ } else if (item.menuType !== "menu") {
5190
+ return currentPath.startsWith(`/wScreens/${item.menuType}`) || currentPath.startsWith(`/wScreens-fallback/${item.menuType}`) || currentPath === "/bp/menu" && new URLSearchParams(hash.replace(/^[#?]/, "")).get("w") === item.menuType;
5191
+ }
5192
+ return false;
5193
+ };
5194
+ var leerRecientes = () => {
5195
+ try {
5196
+ const guardado = JSON.parse(localStorage.getItem(CLAVE_RECIENTES) ?? "[]");
5197
+ return Array.isArray(guardado) ? guardado.filter((clave) => typeof clave === "string") : [];
5198
+ } catch {
5199
+ return [];
5200
+ }
5201
+ };
5202
+ var guardarRecientes = (recientes) => {
5203
+ try {
5204
+ localStorage.setItem(CLAVE_RECIENTES, JSON.stringify(recientes));
5205
+ } catch {
5206
+ }
5207
+ };
5208
+ var MenuListItem = ({ item, level, grupos = [], hermanos = [], onMenuItemClick }) => {
4973
5209
  const navigate = useNavigate2();
4974
5210
  const location = useLocation2();
4975
- const open = useSubMenuOpenState(item.name);
5211
+ const clave = claveDeMenu([...grupos, item.name]);
5212
+ const open = useSubMenuOpenState(clave);
4976
5213
  const dispatch = useAppDispatch();
4977
5214
  const handleClick = () => {
4978
5215
  if (item.menuType === "menu") {
4979
- dispatch(toggleSubMenu(item.name));
5216
+ if (open) {
5217
+ dispatch(setSubMenuOpen({ menuName: clave, isOpen: false }));
5218
+ } else {
5219
+ dispatch(abrirGrupo({ clave, hermanos }));
5220
+ }
4980
5221
  } else {
4981
- navigate(rutaDeItemDeMenu(item));
5222
+ navigate(rutaDeItemDeMenu(item, grupos));
4982
5223
  if (onMenuItemClick) {
4983
5224
  onMenuItemClick();
4984
5225
  }
4985
5226
  }
4986
5227
  };
4987
- const getIcon = (item2) => {
4988
- switch (item2.menuType) {
4989
- case "table":
4990
- return /* @__PURE__ */ jsx24(TableChartIcon, { sx: { color: blue[700] } });
4991
- case "proc":
4992
- return /* @__PURE__ */ jsx24(DnsIcon, { sx: { color: teal[700] } });
4993
- case "menu":
4994
- return /* @__PURE__ */ jsx24(FolderIcon, { sx: { color: orange[800] } });
4995
- default:
4996
- const WScreenComponent = wScreens[item2.menuType];
4997
- if (WScreenComponent !== void 0) {
4998
- return /* @__PURE__ */ jsx24(DesktopWindowsIcon, { sx: { color: grey[700] } });
4999
- }
5000
- return /* @__PURE__ */ jsx24(WarningAmberIcon3, { sx: { color: red[500] } });
5001
- }
5002
- };
5003
- const currentPath = location.pathname;
5004
- let isSelected = false;
5005
- const pathParts = currentPath.split("/");
5006
- const urlTableName = pathParts[1] === "table" ? pathParts[2] : null;
5007
- if (item.menuType === "table") {
5008
- isSelected = currentPath === `/menu/${item.name}` || urlTableName === (item.table || item.name);
5009
- } else if (item.menuType === "proc") {
5010
- isSelected = currentPath === `/procedures/${item.name}`;
5011
- } else if (item.menuType !== "menu") {
5012
- isSelected = currentPath.startsWith(`/wScreens/${item.menuType}`) || currentPath.startsWith(`/wScreens-fallback/${item.menuType}`);
5013
- }
5228
+ const isSelected = estaSeleccionado(item, grupos, location.pathname, location.hash);
5014
5229
  const itemRef = React19.useRef(null);
5015
5230
  useEffect8(() => {
5016
5231
  if (isSelected && itemRef.current) {
5017
5232
  itemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
5018
5233
  }
5019
5234
  }, [isSelected]);
5020
- return /* @__PURE__ */ jsxs18("div", { ref: itemRef, children: [
5021
- /* @__PURE__ */ jsxs18(
5022
- ListItemButton3,
5235
+ const contenido = item.menuType === "menu" ? item.menuContent : [];
5236
+ return /* @__PURE__ */ jsxs19("div", { ref: itemRef, children: [
5237
+ /* @__PURE__ */ jsxs19(
5238
+ ListItemButton4,
5023
5239
  {
5024
5240
  onClick: handleClick,
5025
5241
  sx: { pl: level * 2 },
5026
5242
  selected: isSelected,
5027
5243
  children: [
5028
- /* @__PURE__ */ jsx24(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(item) }),
5029
- /* @__PURE__ */ jsx24(ListItemText2, { primary: item.label || item.name }),
5030
- item.menuType === "menu" ? open ? /* @__PURE__ */ jsx24(ExpandLess, {}) : /* @__PURE__ */ jsx24(ExpandMore, {}) : null
5244
+ /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(item, isSelected || open) }),
5245
+ /* @__PURE__ */ jsx25(ListItemText3, { primary: etiquetaDeItem(item) }),
5246
+ item.menuType === "menu" ? open ? /* @__PURE__ */ jsx25(ExpandLess, {}) : /* @__PURE__ */ jsx25(ExpandMore, {}) : null
5031
5247
  ]
5032
5248
  }
5033
5249
  ),
5034
- item.menuType === "menu" && /* @__PURE__ */ jsx24(Collapse, { in: open, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx24(List3, { component: "div", disablePadding: true, children: item.menuContent.map((subItem) => /* @__PURE__ */ jsx24(
5250
+ item.menuType === "menu" && /* @__PURE__ */ jsx25(Collapse, { in: open, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx25(List4, { component: "div", disablePadding: true, children: contenido.map((subItem) => /* @__PURE__ */ jsx25(
5035
5251
  MenuListItem,
5036
5252
  {
5037
5253
  item: subItem,
5038
5254
  level: level + 1,
5255
+ grupos: [...grupos, item.name],
5256
+ hermanos: contenido.filter((otro) => otro !== subItem).map((otro) => claveDeMenu([...grupos, item.name, otro.name])),
5039
5257
  onMenuItemClick
5040
5258
  },
5041
5259
  subItem.name
5042
5260
  )) }) })
5043
5261
  ] });
5044
5262
  };
5263
+ var ItemPlano = ({ hoja, seleccionado, traerALaVista = false, onElegir }) => {
5264
+ const boton = useRef9(null);
5265
+ useEffect8(() => {
5266
+ if (traerALaVista && seleccionado) boton.current?.scrollIntoView({ block: "nearest" });
5267
+ }, [traerALaVista, seleccionado]);
5268
+ return /* @__PURE__ */ jsxs19(ListItemButton4, { dense: true, selected: seleccionado, onClick: () => onElegir(hoja), ref: boton, children: [
5269
+ /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(hoja.item, seleccionado) }),
5270
+ /* @__PURE__ */ jsx25(
5271
+ ListItemText3,
5272
+ {
5273
+ primary: etiquetaDeItem(hoja.item),
5274
+ secondary: hoja.etiquetasDeGrupos.join(" \u203A ") || void 0,
5275
+ secondaryTypographyProps: { noWrap: true }
5276
+ }
5277
+ )
5278
+ ] });
5279
+ };
5045
5280
  var SideMenu = ({ onMenuItemClick }) => {
5046
5281
  const { clientContext } = useApp();
5047
5282
  const navigate = useNavigate2();
5048
5283
  const location = useLocation2();
5049
5284
  const dispatch = useAppDispatch();
5050
- const subMenuOpenStates = useAppSelector((state) => state.menuUi.subMenuOpenStates);
5051
- const allSubMenusKeys = Object.keys(subMenuOpenStates);
5052
- const openSubMenusCount = allSubMenusKeys.filter((key) => subMenuOpenStates[key]).length;
5053
- const isMostlyOpen = allSubMenusKeys.length > 0 && openSubMenusCount / allSubMenusKeys.length > 0.5;
5054
- const handleToggleAllSubMenus = () => {
5055
- dispatch(setAllSubMenusOpen(!isMostlyOpen));
5056
- };
5057
- if (!clientContext || !clientContext.menu) {
5058
- return /* @__PURE__ */ jsx24(Box13, { sx: { p: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx24(Typography11, { variant: "body2", color: "text.secondary", children: "Cargando men\xFA..." }) });
5059
- }
5285
+ const [consulta, setConsulta] = useState18("");
5286
+ const [indice, setIndice] = useState18(0);
5287
+ const [recientes, setRecientes] = useState18(leerRecientes);
5288
+ const campoDeBusqueda = useRef9(null);
5289
+ const menu = clientContext?.menu;
5290
+ const hojas = useMemo12(() => hojasDeMenu(menu ?? []), [menu]);
5291
+ const resultados = useMemo12(() => buscarEnMenu(hojas, consulta), [hojas, consulta]);
5060
5292
  useEffect8(() => {
5061
- if (!clientContext?.menu) return;
5062
- const expandParents = (items, targetPath, parentNames = []) => {
5063
- const pathParts = targetPath.split("/");
5064
- const urlTableName = pathParts[1] === "table" ? pathParts[2] : null;
5065
- for (const item of items) {
5066
- let matches = false;
5067
- if (item.menuType === "table") {
5068
- matches = targetPath === `/menu/${item.name}` || urlTableName === (item.table || item.name);
5069
- } else if (item.menuType === "proc") {
5070
- matches = targetPath === `/procedures/${item.name}`;
5071
- } else if (item.menuType !== "menu") {
5072
- matches = targetPath.startsWith(`/wScreens/${item.menuType}`) || targetPath.startsWith(`/wScreens-fallback/${item.menuType}`);
5073
- }
5074
- if (matches) {
5075
- parentNames.forEach((name) => {
5076
- dispatch(setSubMenuOpen({ menuName: name, isOpen: true }));
5077
- });
5078
- return true;
5079
- }
5080
- if (item.menuType === "menu") {
5081
- const found = expandParents(item.menuContent, targetPath, [...parentNames, item.name]);
5082
- if (found) return true;
5083
- }
5293
+ const alApretar = (evento) => {
5294
+ if ((evento.ctrlKey || evento.metaKey) && !evento.altKey && !evento.shiftKey && evento.key.toLowerCase() === "k") {
5295
+ evento.preventDefault();
5296
+ dispatch(setDrawerOpen(true));
5297
+ setTimeout(() => {
5298
+ campoDeBusqueda.current?.focus();
5299
+ campoDeBusqueda.current?.select();
5300
+ }, 50);
5084
5301
  }
5085
- return false;
5086
5302
  };
5087
- expandParents(clientContext.menu, location.pathname);
5088
- }, [location.pathname, clientContext, dispatch]);
5089
- return /* @__PURE__ */ jsxs18(Box13, { sx: { width: "100%", flexShrink: 0 }, children: [
5090
- clientContext.menu.some((item) => item.menuType === "menu") && /* @__PURE__ */ jsx24(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs18(ListItemButton3, { onClick: handleToggleAllSubMenus, children: [
5091
- /* @__PURE__ */ jsx24(ListItemIcon2, { sx: { minWidth: "38px" }, children: isMostlyOpen ? /* @__PURE__ */ jsx24(UnfoldLessIcon, {}) : /* @__PURE__ */ jsx24(UnfoldMoreIcon, {}) }),
5092
- /* @__PURE__ */ jsx24(ListItemText2, { primary: isMostlyOpen ? "colapsar " : "expandir" })
5093
- ] }) }),
5094
- /* @__PURE__ */ jsx24(Divider2, {}),
5095
- /* @__PURE__ */ jsx24(List3, { children: clientContext.menu.map((menuItem) => /* @__PURE__ */ jsx24(
5096
- MenuListItem,
5303
+ window.addEventListener("keydown", alApretar);
5304
+ return () => window.removeEventListener("keydown", alApretar);
5305
+ }, [dispatch]);
5306
+ useEffect8(() => {
5307
+ const actual = hojas.find((hoja) => estaSeleccionado(hoja.item, hoja.grupos, location.pathname, location.hash));
5308
+ if (!actual) return;
5309
+ dispatch(abrirCamino(actual.grupos.map((_, i) => claveDeMenu(actual.grupos.slice(0, i + 1)))));
5310
+ setRecientes((anteriores) => {
5311
+ if (anteriores.includes(actual.clave)) return anteriores;
5312
+ const nuevos = [actual.clave, ...anteriores].slice(0, MAXIMO_RECIENTES);
5313
+ guardarRecientes(nuevos);
5314
+ return nuevos;
5315
+ });
5316
+ }, [location.pathname, location.hash, hojas, dispatch]);
5317
+ if (!menu) {
5318
+ return /* @__PURE__ */ jsx25(Box14, { sx: { p: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx25(Typography12, { variant: "body2", color: "text.secondary", children: "Cargando men\xFA..." }) });
5319
+ }
5320
+ const elegir = (hoja) => {
5321
+ navigate(rutaDeItemDeMenu(hoja.item, hoja.grupos));
5322
+ setConsulta("");
5323
+ setIndice(0);
5324
+ if (onMenuItemClick) {
5325
+ onMenuItemClick();
5326
+ }
5327
+ };
5328
+ const alApretarEnBusqueda = (evento) => {
5329
+ if (evento.key === "ArrowDown" || evento.key === "ArrowUp") {
5330
+ evento.preventDefault();
5331
+ if (resultados.length === 0) return;
5332
+ setIndice((actual) => (actual + (evento.key === "ArrowDown" ? 1 : -1) + resultados.length) % resultados.length);
5333
+ } else if (evento.key === "Enter") {
5334
+ evento.preventDefault();
5335
+ const hoja = resultados[indice];
5336
+ if (hoja) elegir(hoja);
5337
+ } else if (evento.key === "Escape" && consulta !== "") {
5338
+ evento.stopPropagation();
5339
+ setConsulta("");
5340
+ }
5341
+ };
5342
+ const hojasRecientes = recientes.map((clave) => hojas.find((hoja) => hoja.clave === clave)).filter((hoja) => hoja !== void 0);
5343
+ return /* @__PURE__ */ jsxs19(Box14, { sx: { width: "100%", flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
5344
+ /* @__PURE__ */ jsx25(Box14, { sx: { px: 1.5, py: 1, flexShrink: 0 }, children: /* @__PURE__ */ jsx25(
5345
+ TextField2,
5097
5346
  {
5098
- item: menuItem,
5099
- level: 1,
5100
- onMenuItemClick
5101
- },
5102
- menuItem.name
5103
- )) })
5347
+ inputRef: campoDeBusqueda,
5348
+ value: consulta,
5349
+ onChange: (evento) => {
5350
+ setConsulta(evento.target.value);
5351
+ setIndice(0);
5352
+ },
5353
+ onKeyDown: alApretarEnBusqueda,
5354
+ placeholder: "Buscar (Ctrl+K)",
5355
+ size: "small",
5356
+ fullWidth: true,
5357
+ autoComplete: "off",
5358
+ sx: { "& .MuiInputBase-input": { fontSize: "0.8rem" } },
5359
+ InputProps: {
5360
+ startAdornment: /* @__PURE__ */ jsx25(InputAdornment2, { position: "start", children: /* @__PURE__ */ jsx25(SearchIcon3, { fontSize: "small" }) }),
5361
+ endAdornment: consulta !== "" ? /* @__PURE__ */ jsx25(InputAdornment2, { position: "end", children: /* @__PURE__ */ jsx25(IconButton7, { size: "small", "aria-label": "borrar b\xFAsqueda", edge: "end", onClick: () => {
5362
+ setConsulta("");
5363
+ campoDeBusqueda.current?.focus();
5364
+ }, children: /* @__PURE__ */ jsx25(ClearIcon, { fontSize: "small" }) }) }) : void 0
5365
+ }
5366
+ }
5367
+ ) }),
5368
+ /* @__PURE__ */ jsx25(Divider2, {}),
5369
+ /* @__PURE__ */ jsx25(Box14, { sx: { flex: 1, minHeight: 0, overflowY: "auto" }, children: consulta.trim() !== "" ? resultados.length === 0 ? /* @__PURE__ */ jsx25(Box14, { sx: { p: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx25(Typography12, { variant: "body2", color: "text.secondary", children: "Sin resultados" }) }) : /* @__PURE__ */ jsx25(List4, { children: resultados.map((hoja, i) => /* @__PURE__ */ jsx25(ItemPlano, { hoja, seleccionado: i === indice, traerALaVista: true, onElegir: elegir }, hoja.clave)) }) : /* @__PURE__ */ jsxs19(Fragment5, { children: [
5370
+ hojasRecientes.length > 0 && /* @__PURE__ */ jsxs19(Fragment5, { children: [
5371
+ /* @__PURE__ */ jsx25(Typography12, { variant: "overline", color: "text.secondary", sx: { display: "block", px: 2, pt: 1, lineHeight: 2 }, children: "Recientes" }),
5372
+ /* @__PURE__ */ jsx25(List4, { disablePadding: true, children: hojasRecientes.map((hoja) => /* @__PURE__ */ jsx25(
5373
+ ItemPlano,
5374
+ {
5375
+ hoja,
5376
+ seleccionado: estaSeleccionado(hoja.item, hoja.grupos, location.pathname, location.hash),
5377
+ onElegir: elegir
5378
+ },
5379
+ hoja.clave
5380
+ )) }),
5381
+ /* @__PURE__ */ jsx25(Divider2, {})
5382
+ ] }),
5383
+ /* @__PURE__ */ jsx25(List4, { children: menu.map((menuItem) => /* @__PURE__ */ jsx25(
5384
+ MenuListItem,
5385
+ {
5386
+ item: menuItem,
5387
+ level: 1,
5388
+ hermanos: menu.filter((otro) => otro !== menuItem).map((otro) => claveDeMenu([otro.name])),
5389
+ onMenuItemClick
5390
+ },
5391
+ menuItem.name
5392
+ )) })
5393
+ ] }) })
5104
5394
  ] });
5105
5395
  };
5106
5396
  var SideMenu_default = SideMenu;
@@ -5110,10 +5400,11 @@ import { useCallback as useCallback14 } from "react";
5110
5400
  import { useNavigate as useNavigate3 } from "react-router-dom";
5111
5401
  import { useDispatch as useDispatch3 } from "react-redux";
5112
5402
  var useLogout = () => {
5113
- const { setIsLoggedIn } = useApp();
5403
+ const { setIsLoggedIn, avisarCierreVoluntario } = useApp();
5114
5404
  const dispatch = useDispatch3();
5115
5405
  const navigate = useNavigate3();
5116
5406
  const logout = useCallback14(async () => {
5407
+ avisarCierreVoluntario(true);
5117
5408
  try {
5118
5409
  const response = await fetchApi("/logout", { method: "GET" });
5119
5410
  if (response.status === 401 || response.ok) {
@@ -5126,19 +5417,21 @@ var useLogout = () => {
5126
5417
  } finally {
5127
5418
  setIsLoggedIn(false);
5128
5419
  dispatch(clearClientContext());
5420
+ dispatch(setRedirectPath(null));
5129
5421
  navigate("/login", { replace: true });
5130
5422
  }
5131
- }, [setIsLoggedIn, dispatch, navigate]);
5423
+ }, [setIsLoggedIn, avisarCierreVoluntario, dispatch, navigate]);
5132
5424
  return logout;
5133
5425
  };
5134
5426
  var useLogout_default = useLogout;
5135
5427
 
5136
5428
  // src/components/MainLayout.tsx
5137
- import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
5429
+ import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
5138
5430
  var drawerWidth = 240;
5139
5431
  var MainLayout = () => {
5140
5432
  const { clientContext } = useApp();
5141
5433
  const logout = useLogout_default();
5434
+ const navigate = useNavigate4();
5142
5435
  const isDrawerOpen = useIsDrawerOpen();
5143
5436
  const dispatch = useAppDispatch();
5144
5437
  const theme = useTheme5();
@@ -5157,11 +5450,11 @@ var MainLayout = () => {
5157
5450
  }
5158
5451
  };
5159
5452
  if (!clientContext) {
5160
- return /* @__PURE__ */ jsx25(Box14, { sx: { display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }, children: /* @__PURE__ */ jsx25(Typography12, { children: "Cargando dise\xF1o de la aplicaci\xF3n..." }) });
5453
+ return /* @__PURE__ */ jsx26(Box15, { sx: { display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }, children: /* @__PURE__ */ jsx26(Typography13, { children: "Cargando dise\xF1o de la aplicaci\xF3n..." }) });
5161
5454
  }
5162
- return /* @__PURE__ */ jsxs19(Box14, { sx: { display: "flex", height: "100vh" }, children: [
5163
- /* @__PURE__ */ jsx25(CssBaseline, {}),
5164
- /* @__PURE__ */ jsx25(
5455
+ return /* @__PURE__ */ jsxs20(Box15, { sx: { display: "flex", height: "100vh" }, children: [
5456
+ /* @__PURE__ */ jsx26(CssBaseline, {}),
5457
+ /* @__PURE__ */ jsx26(
5165
5458
  AppBar,
5166
5459
  {
5167
5460
  position: "fixed",
@@ -5180,9 +5473,9 @@ var MainLayout = () => {
5180
5473
  })
5181
5474
  }
5182
5475
  },
5183
- children: /* @__PURE__ */ jsxs19(Toolbar, { children: [
5184
- /* @__PURE__ */ jsx25(
5185
- IconButton6,
5476
+ children: /* @__PURE__ */ jsxs20(Toolbar, { children: [
5477
+ /* @__PURE__ */ jsx26(
5478
+ IconButton8,
5186
5479
  {
5187
5480
  color: "inherit",
5188
5481
  "aria-label": "open drawer",
@@ -5191,10 +5484,10 @@ var MainLayout = () => {
5191
5484
  sx: {
5192
5485
  marginRight: 2
5193
5486
  },
5194
- children: isDrawerOpen ? /* @__PURE__ */ jsx25(ChevronLeftIcon, {}) : /* @__PURE__ */ jsx25(MenuIcon, {})
5487
+ children: isDrawerOpen ? /* @__PURE__ */ jsx26(ChevronLeftIcon, {}) : /* @__PURE__ */ jsx26(MenuIcon, {})
5195
5488
  }
5196
5489
  ),
5197
- !isDrawerOpen && /* @__PURE__ */ jsx25(
5490
+ !isDrawerOpen && /* @__PURE__ */ jsx26(
5198
5491
  "img",
5199
5492
  {
5200
5493
  src: `${envConfig.backendUrl}/img/logo-128.png`,
@@ -5202,21 +5495,22 @@ var MainLayout = () => {
5202
5495
  style: { height: "32px", marginRight: "12px", objectFit: "contain" }
5203
5496
  }
5204
5497
  ),
5205
- /* @__PURE__ */ jsx25(Typography12, { variant: "h6", noWrap: true, component: "div", sx: { flexGrow: 1 }, children: isDrawerOpen ? "" : clientContext.config.title }),
5206
- /* @__PURE__ */ jsx25(Typography12, { variant: "subtitle1", component: "div", sx: { mr: 2 }, children: clientContext.username }),
5207
- /* @__PURE__ */ jsx25(
5498
+ /* @__PURE__ */ jsx26(Typography13, { variant: "h6", noWrap: true, component: "div", sx: { flexGrow: 1 }, children: isDrawerOpen ? "" : clientContext.config.title }),
5499
+ /* @__PURE__ */ jsx26(Typography13, { variant: "subtitle1", component: "div", sx: { mr: 2 }, children: clientContext.username }),
5500
+ /* @__PURE__ */ jsx26(IconButton8, { color: "inherit", title: "Cambiar contrase\xF1a", "aria-label": "cambiar contrase\xF1a", onClick: () => navigate("/chpass"), sx: { mr: 1 }, children: /* @__PURE__ */ jsx26(VpnKeyIcon, {}) }),
5501
+ /* @__PURE__ */ jsx26(
5208
5502
  Button8,
5209
5503
  {
5210
5504
  color: "inherit",
5211
5505
  onClick: logout,
5212
- startIcon: /* @__PURE__ */ jsx25(LogoutIcon, {}),
5506
+ startIcon: /* @__PURE__ */ jsx26(LogoutIcon, {}),
5213
5507
  children: "Salir"
5214
5508
  }
5215
5509
  )
5216
5510
  ] })
5217
5511
  }
5218
5512
  ),
5219
- /* @__PURE__ */ jsxs19(
5513
+ /* @__PURE__ */ jsxs20(
5220
5514
  Drawer,
5221
5515
  {
5222
5516
  variant: "persistent",
@@ -5228,12 +5522,13 @@ var MainLayout = () => {
5228
5522
  "& .MuiDrawer-paper": {
5229
5523
  width: drawerWidth,
5230
5524
  boxSizing: "border-box",
5231
- overflowX: "hidden",
5232
- overflowY: "auto"
5525
+ overflow: "hidden",
5526
+ display: "flex",
5527
+ flexDirection: "column"
5233
5528
  }
5234
5529
  },
5235
5530
  children: [
5236
- /* @__PURE__ */ jsxs19(
5531
+ /* @__PURE__ */ jsxs20(
5237
5532
  Toolbar,
5238
5533
  {
5239
5534
  sx: {
@@ -5241,12 +5536,14 @@ var MainLayout = () => {
5241
5536
  alignItems: "center",
5242
5537
  justifyContent: "flex-start",
5243
5538
  px: 1,
5539
+ flexShrink: 0,
5540
+ // el logo queda fijo: solo scrollea la lista del menú
5244
5541
  fontSize: "1.3rem",
5245
5542
  minHeight: theme.mixins.toolbar.minHeight,
5246
5543
  gap: 1.5
5247
5544
  },
5248
5545
  children: [
5249
- /* @__PURE__ */ jsx25(
5546
+ /* @__PURE__ */ jsx26(
5250
5547
  "img",
5251
5548
  {
5252
5549
  src: `${envConfig.backendUrl}/img/logo-128.png`,
@@ -5254,16 +5551,16 @@ var MainLayout = () => {
5254
5551
  style: { height: "32px", objectFit: "contain" }
5255
5552
  }
5256
5553
  ),
5257
- /* @__PURE__ */ jsx25(Typography12, { variant: "h6", sx: { fontWeight: "bold" }, children: clientContext.config.title })
5554
+ /* @__PURE__ */ jsx26(Typography13, { variant: "h6", sx: { fontWeight: "bold" }, children: clientContext.config.title })
5258
5555
  ]
5259
5556
  }
5260
5557
  ),
5261
- /* @__PURE__ */ jsx25(SideMenu_default, { onMenuItemClick: handleDrawerCloseMobile })
5558
+ /* @__PURE__ */ jsx26(SideMenu_default, { onMenuItemClick: handleDrawerCloseMobile })
5262
5559
  ]
5263
5560
  }
5264
5561
  ),
5265
- /* @__PURE__ */ jsxs19(
5266
- Box14,
5562
+ /* @__PURE__ */ jsxs20(
5563
+ Box15,
5267
5564
  {
5268
5565
  component: "main",
5269
5566
  sx: {
@@ -5289,8 +5586,8 @@ var MainLayout = () => {
5289
5586
  width: isDrawerOpen ? `calc(100% - ${drawerWidth}px)` : "100%"
5290
5587
  },
5291
5588
  children: [
5292
- /* @__PURE__ */ jsx25(Toolbar, {}),
5293
- /* @__PURE__ */ jsx25(Box14, { sx: { flex: 1, minHeight: 0, overflowY: "auto", display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsx25(Outlet, {}) })
5589
+ /* @__PURE__ */ jsx26(Toolbar, {}),
5590
+ /* @__PURE__ */ jsx26(Box15, { sx: { flex: 1, minHeight: 0, overflowY: "auto", display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsx26(Outlet, {}) })
5294
5591
  ]
5295
5592
  }
5296
5593
  )
@@ -5299,8 +5596,8 @@ var MainLayout = () => {
5299
5596
  var MainLayout_default = MainLayout;
5300
5597
 
5301
5598
  // src/components/SessionExpiredMessage.tsx
5302
- import { Box as Box15, Typography as Typography13, Button as Button9, Modal } from "@mui/material";
5303
- import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
5599
+ import { Box as Box16, Typography as Typography14, Button as Button9, Modal } from "@mui/material";
5600
+ import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
5304
5601
  var style = {
5305
5602
  position: "absolute",
5306
5603
  top: "50%",
@@ -5319,7 +5616,7 @@ var SessionExpiredMessage = () => {
5319
5616
  const handleClose = () => {
5320
5617
  handleExpiredSessionRedirect();
5321
5618
  };
5322
- return /* @__PURE__ */ jsx26(
5619
+ return /* @__PURE__ */ jsx27(
5323
5620
  Modal,
5324
5621
  {
5325
5622
  open: showSessionExpiredMessage,
@@ -5339,10 +5636,10 @@ var SessionExpiredMessage = () => {
5339
5636
  }
5340
5637
  }
5341
5638
  },
5342
- children: /* @__PURE__ */ jsxs20(Box15, { sx: style, children: [
5343
- /* @__PURE__ */ jsx26(Typography13, { id: "session-expired-modal-title", variant: "h6", component: "h2", gutterBottom: true, children: "\xA1Sesi\xF3n Expirada!" }),
5344
- /* @__PURE__ */ jsx26(Typography13, { id: "session-expired-modal-description", sx: { mt: 2, mb: 3 }, children: "Tu sesi\xF3n ha expirado por inactividad o por seguridad. Por favor, inicia sesi\xF3n de nuevo." }),
5345
- /* @__PURE__ */ jsx26(Button9, { variant: "contained", color: "primary", onClick: handleClose, children: "Ir al Login" })
5639
+ children: /* @__PURE__ */ jsxs21(Box16, { sx: style, children: [
5640
+ /* @__PURE__ */ jsx27(Typography14, { id: "session-expired-modal-title", variant: "h6", component: "h2", gutterBottom: true, children: "\xA1Sesi\xF3n Expirada!" }),
5641
+ /* @__PURE__ */ jsx27(Typography14, { id: "session-expired-modal-description", sx: { mt: 2, mb: 3 }, children: "Tu sesi\xF3n ha expirado por inactividad o por seguridad. Por favor, inicia sesi\xF3n de nuevo." }),
5642
+ /* @__PURE__ */ jsx27(Button9, { variant: "contained", color: "primary", onClick: handleClose, children: "Ir al Login" })
5346
5643
  ] })
5347
5644
  }
5348
5645
  );
@@ -5351,24 +5648,24 @@ var SessionExpiredMessage_default = SessionExpiredMessage;
5351
5648
 
5352
5649
  // src/pages/PantallaInicial.tsx
5353
5650
  import { Navigate } from "react-router-dom";
5354
- import { jsx as jsx27 } from "react/jsx-runtime";
5651
+ import { jsx as jsx28 } from "react/jsx-runtime";
5355
5652
  var PantallaInicial = () => {
5356
5653
  const { clientContext } = useApp();
5357
5654
  const item = clientContext.menu ? itemInicial(clientContext.menu) : void 0;
5358
- return item ? /* @__PURE__ */ jsx27(Navigate, { to: rutaDeItemDeMenu(item), replace: true }) : null;
5655
+ return item && clientContext.menu ? /* @__PURE__ */ jsx28(Navigate, { to: rutaDeItemDeMenu(item, gruposDeItem(clientContext.menu, item)), replace: true }) : null;
5359
5656
  };
5360
5657
  var PantallaInicial_default = PantallaInicial;
5361
5658
 
5362
5659
  // src/pages/LogoutPage.tsx
5363
5660
  import { useEffect as useEffect10 } from "react";
5364
- import { Box as Box16, Typography as Typography14, CircularProgress as CircularProgress5 } from "@mui/material";
5365
- import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
5661
+ import { Box as Box17, Typography as Typography15, CircularProgress as CircularProgress5 } from "@mui/material";
5662
+ import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
5366
5663
  var LogoutPage = () => {
5367
5664
  const logout = useLogout_default();
5368
5665
  useEffect10(() => {
5369
5666
  logout();
5370
5667
  }, [logout]);
5371
- return /* @__PURE__ */ jsxs21(Box16, { sx: {
5668
+ return /* @__PURE__ */ jsxs22(Box17, { sx: {
5372
5669
  display: "flex",
5373
5670
  flexDirection: "column",
5374
5671
  justifyContent: "center",
@@ -5376,20 +5673,31 @@ var LogoutPage = () => {
5376
5673
  height: "100vh",
5377
5674
  textAlign: "center"
5378
5675
  }, children: [
5379
- /* @__PURE__ */ jsx28(CircularProgress5, { size: 60 }),
5380
- /* @__PURE__ */ jsx28(Typography14, { variant: "h6", sx: { mt: 2 }, children: "Cerrando sesi\xF3n..." })
5676
+ /* @__PURE__ */ jsx29(CircularProgress5, { size: 60 }),
5677
+ /* @__PURE__ */ jsx29(Typography15, { variant: "h6", sx: { mt: 2 }, children: "Cerrando sesi\xF3n..." })
5381
5678
  ] });
5382
5679
  };
5383
5680
  var LogoutPage_default = LogoutPage;
5384
5681
 
5385
5682
  // src/pages/LoginPage.tsx
5386
- import { useState as useState18 } from "react";
5387
- import { Button as Button10, TextField as TextField2, Typography as Typography15, Alert as Alert3, AlertTitle, Box as Box17, CircularProgress as CircularProgress6 } from "@mui/material";
5683
+ import { useState as useState19 } from "react";
5684
+ import { Button as Button10, TextField as TextField3, Typography as Typography16, Alert as Alert3, AlertTitle, Box as Box18, CircularProgress as CircularProgress6, Link } from "@mui/material";
5685
+ import { useLocation as useLocation3 } from "react-router-dom";
5686
+
5687
+ // src/pages/configuracionLogin.ts
5688
+ var RUTA_POR_OMISION = "/new-pass";
5689
+ var rutaDeRecuperarClave = null;
5690
+ var configurarRecuperarClave = (recuperarClave) => {
5691
+ rutaDeRecuperarClave = recuperarClave === true ? RUTA_POR_OMISION : typeof recuperarClave === "string" && recuperarClave !== "" ? recuperarClave : null;
5692
+ };
5693
+ var rutaParaRecuperarClave = () => rutaDeRecuperarClave;
5694
+
5695
+ // src/pages/LoginPage.tsx
5388
5696
  import { useForm, Controller } from "react-hook-form";
5389
5697
  import { zodResolver } from "@hookform/resolvers/zod";
5390
5698
  import * as z from "zod";
5391
5699
  import { useDispatch as useDispatch4 } from "react-redux";
5392
- import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
5700
+ import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
5393
5701
  var loginSchema = z.object({
5394
5702
  username: z.string().min(1, "El usuario es requerido"),
5395
5703
  // Añadir validación básica
@@ -5397,9 +5705,12 @@ var loginSchema = z.object({
5397
5705
  // Añadir validación básica
5398
5706
  });
5399
5707
  var LoginPage = () => {
5400
- const [loginError, setLoginError] = useState18(null);
5401
- const [isSubmitting, setIsSubmitting] = useState18(false);
5402
- const { setIsLoggedIn, checkSession } = useApp();
5708
+ const [loginError, setLoginError] = useState19(null);
5709
+ const [isSubmitting, setIsSubmitting] = useState19(false);
5710
+ const estadoDeLaRuta = useLocation3().state;
5711
+ const claveCambiada = typeof estadoDeLaRuta === "object" && estadoDeLaRuta !== null && "claveCambiada" in estadoDeLaRuta && estadoDeLaRuta.claveCambiada === true;
5712
+ const rutaDeRecuperarClave2 = rutaParaRecuperarClave();
5713
+ const { setIsLoggedIn, checkSession, avisarCierreVoluntario } = useApp();
5403
5714
  const dispatch = useDispatch4();
5404
5715
  const {
5405
5716
  control,
@@ -5431,6 +5742,7 @@ var LoginPage = () => {
5431
5742
  const errorText = "Credenciales incorrectas o error al iniciar sesi\xF3n";
5432
5743
  throw new Error(errorText);
5433
5744
  }
5745
+ avisarCierreVoluntario(false);
5434
5746
  setIsLoggedIn(true);
5435
5747
  await dispatch(fetchClientContext()).unwrap();
5436
5748
  console.log("Login exitoso y clientContext cargado.");
@@ -5443,16 +5755,16 @@ var LoginPage = () => {
5443
5755
  setIsSubmitting(false);
5444
5756
  }
5445
5757
  };
5446
- return /* @__PURE__ */ jsx29(
5447
- Box17,
5758
+ return /* @__PURE__ */ jsx30(
5759
+ Box18,
5448
5760
  {
5449
5761
  display: "flex",
5450
5762
  alignItems: "center",
5451
5763
  justifyContent: "center",
5452
5764
  minHeight: "100vh",
5453
5765
  bgcolor: "#f5f5f5",
5454
- children: /* @__PURE__ */ jsxs22(
5455
- Box17,
5766
+ children: /* @__PURE__ */ jsxs23(
5767
+ Box18,
5456
5768
  {
5457
5769
  width: "100%",
5458
5770
  maxWidth: "400px",
@@ -5461,7 +5773,7 @@ var LoginPage = () => {
5461
5773
  borderRadius: 8,
5462
5774
  boxShadow: 3,
5463
5775
  children: [
5464
- /* @__PURE__ */ jsx29(Box17, { sx: { display: "flex", justifyContent: "center", mb: 3 }, children: /* @__PURE__ */ jsx29(
5776
+ /* @__PURE__ */ jsx30(Box18, { sx: { display: "flex", justifyContent: "center", mb: 3 }, children: /* @__PURE__ */ jsx30(
5465
5777
  "img",
5466
5778
  {
5467
5779
  src: `${envConfig.backendUrl}/img/login-logo-icon.png`,
@@ -5469,62 +5781,81 @@ var LoginPage = () => {
5469
5781
  style: { height: "192px", maxWidth: "100%", objectFit: "contain" }
5470
5782
  }
5471
5783
  ) }),
5472
- /* @__PURE__ */ jsx29(Typography15, { variant: "h5", align: "center", gutterBottom: true, sx: { fontWeight: "bold", color: "primary.main" }, children: "Iniciar Sesi\xF3n" }),
5473
- /* @__PURE__ */ jsxs22("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-4", children: [
5474
- /* @__PURE__ */ jsxs22("div", { children: [
5475
- /* @__PURE__ */ jsx29("label", { htmlFor: "username", children: /* @__PURE__ */ jsx29(Typography15, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Usuario" }) }),
5476
- /* @__PURE__ */ jsx29(
5477
- Controller,
5478
- {
5479
- name: "username",
5480
- control,
5481
- render: ({ field }) => /* @__PURE__ */ jsx29(
5482
- TextField2,
5784
+ /* @__PURE__ */ jsx30(Typography16, { variant: "h5", align: "center", gutterBottom: true, sx: { fontWeight: "bold", color: "primary.main" }, children: "Iniciar Sesi\xF3n" }),
5785
+ /* @__PURE__ */ jsxs23(
5786
+ "form",
5787
+ {
5788
+ onSubmit: handleSubmit(onSubmit),
5789
+ className: "space-y-4",
5790
+ onKeyDown: (evento) => {
5791
+ if (evento.key !== "Enter" || evento.shiftKey || evento.ctrlKey || evento.altKey || evento.metaKey) return;
5792
+ if (!(evento.target instanceof HTMLInputElement)) return;
5793
+ evento.preventDefault();
5794
+ if (!isSubmitting) void handleSubmit(onSubmit)();
5795
+ },
5796
+ children: [
5797
+ /* @__PURE__ */ jsxs23("div", { children: [
5798
+ /* @__PURE__ */ jsx30("label", { htmlFor: "username", children: /* @__PURE__ */ jsx30(Typography16, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Usuario" }) }),
5799
+ /* @__PURE__ */ jsx30(
5800
+ Controller,
5483
5801
  {
5484
- ...field,
5485
- type: "text",
5486
- placeholder: "Ingresa tu usuario",
5487
- fullWidth: true,
5488
- margin: "normal",
5489
- error: !!errors.username,
5490
- helperText: errors.username?.message,
5491
- disabled: isSubmitting
5802
+ name: "username",
5803
+ control,
5804
+ render: ({ field }) => /* @__PURE__ */ jsx30(
5805
+ TextField3,
5806
+ {
5807
+ ...field,
5808
+ type: "text",
5809
+ id: "username",
5810
+ autoComplete: "username",
5811
+ placeholder: "Ingresa tu usuario",
5812
+ fullWidth: true,
5813
+ margin: "normal",
5814
+ error: !!errors.username,
5815
+ helperText: errors.username?.message,
5816
+ disabled: isSubmitting
5817
+ }
5818
+ )
5492
5819
  }
5493
5820
  )
5494
- }
5495
- )
5496
- ] }),
5497
- /* @__PURE__ */ jsxs22("div", { children: [
5498
- /* @__PURE__ */ jsx29("label", { htmlFor: "password", children: /* @__PURE__ */ jsx29(Typography15, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Contrase\xF1a" }) }),
5499
- /* @__PURE__ */ jsx29(
5500
- Controller,
5501
- {
5502
- name: "password",
5503
- control,
5504
- render: ({ field }) => /* @__PURE__ */ jsx29(
5505
- TextField2,
5821
+ ] }),
5822
+ /* @__PURE__ */ jsxs23("div", { children: [
5823
+ /* @__PURE__ */ jsx30("label", { htmlFor: "password", children: /* @__PURE__ */ jsx30(Typography16, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Contrase\xF1a" }) }),
5824
+ /* @__PURE__ */ jsx30(
5825
+ Controller,
5506
5826
  {
5507
- ...field,
5508
- type: "password",
5509
- placeholder: "Ingresa tu contrase\xF1a",
5510
- fullWidth: true,
5511
- margin: "normal",
5512
- error: !!errors.password,
5513
- helperText: errors.password?.message,
5514
- disabled: isSubmitting
5827
+ name: "password",
5828
+ control,
5829
+ render: ({ field }) => /* @__PURE__ */ jsx30(
5830
+ TextField3,
5831
+ {
5832
+ ...field,
5833
+ type: "password",
5834
+ id: "password",
5835
+ autoComplete: "current-password",
5836
+ placeholder: "Ingresa tu contrase\xF1a",
5837
+ fullWidth: true,
5838
+ margin: "normal",
5839
+ error: !!errors.password,
5840
+ helperText: errors.password?.message,
5841
+ disabled: isSubmitting
5842
+ }
5843
+ )
5515
5844
  }
5516
5845
  )
5517
- }
5518
- )
5519
- ] }),
5520
- /* @__PURE__ */ jsx29(Button10, { type: "submit", fullWidth: true, variant: "contained", color: "primary", disabled: isSubmitting, sx: { mt: 2 }, children: isSubmitting ? /* @__PURE__ */ jsxs22(Fragment5, { children: [
5521
- /* @__PURE__ */ jsx29(CircularProgress6, { size: 20, sx: { mr: 1 } }),
5522
- "Iniciando sesi\xF3n..."
5523
- ] }) : "Iniciar Sesi\xF3n" })
5524
- ] }),
5525
- loginError && /* @__PURE__ */ jsxs22(Alert3, { severity: "error", sx: { mt: 2, maxHeight: "200px" }, children: [
5846
+ ] }),
5847
+ /* @__PURE__ */ jsx30(Button10, { type: "submit", fullWidth: true, variant: "contained", color: "primary", disabled: isSubmitting, sx: { mt: 2 }, children: isSubmitting ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
5848
+ /* @__PURE__ */ jsx30(CircularProgress6, { size: 20, sx: { mr: 1 } }),
5849
+ "Iniciando sesi\xF3n..."
5850
+ ] }) : "Iniciar Sesi\xF3n" })
5851
+ ]
5852
+ }
5853
+ ),
5854
+ rutaDeRecuperarClave2 && /* @__PURE__ */ jsx30(Box18, { sx: { mt: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx30(Link, { href: `${envConfig.backendUrl}${rutaDeRecuperarClave2}`, variant: "body2", underline: "hover", children: "Me olvid\xE9 la clave / es mi primer ingreso" }) }),
5855
+ claveCambiada && !loginError && /* @__PURE__ */ jsx30(Alert3, { severity: "success", sx: { mt: 2 }, children: "La contrase\xF1a se cambi\xF3. Ingres\xE1 con la clave nueva." }),
5856
+ loginError && /* @__PURE__ */ jsxs23(Alert3, { severity: "error", sx: { mt: 2, maxHeight: "200px" }, children: [
5526
5857
  " ",
5527
- /* @__PURE__ */ jsx29(AlertTitle, { children: "Error" }),
5858
+ /* @__PURE__ */ jsx30(AlertTitle, { children: "Error" }),
5528
5859
  loginError
5529
5860
  ] })
5530
5861
  ]
@@ -5536,10 +5867,10 @@ var LoginPage = () => {
5536
5867
  var LoginPage_default = LoginPage;
5537
5868
 
5538
5869
  // src/components/ProcedureForm.tsx
5539
- import React23, { useState as useState19, useCallback as useCallback15, useMemo as useMemo12 } from "react";
5870
+ import React23, { useState as useState20, useCallback as useCallback15, useMemo as useMemo13 } from "react";
5540
5871
  import { useParams } from "react-router-dom";
5541
5872
  import {
5542
- TextField as TextField3,
5873
+ TextField as TextField4,
5543
5874
  Button as Button11,
5544
5875
  FormControl as FormControl3,
5545
5876
  InputLabel as InputLabel2,
@@ -5548,8 +5879,8 @@ import {
5548
5879
  Checkbox as Checkbox3,
5549
5880
  FormControlLabel as FormControlLabel3,
5550
5881
  FormHelperText as FormHelperText2,
5551
- Typography as Typography17,
5552
- Box as Box19,
5882
+ Typography as Typography18,
5883
+ Box as Box20,
5553
5884
  CircularProgress as CircularProgress7,
5554
5885
  Alert as Alert4,
5555
5886
  Divider as Divider3,
@@ -5557,14 +5888,14 @@ import {
5557
5888
  } from "@mui/material";
5558
5889
 
5559
5890
  // src/pages/procedure-results/SuccessDisplay.tsx
5560
- import { Box as Box18, Typography as Typography16, Paper as Paper3 } from "@mui/material";
5561
- import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
5891
+ import { Box as Box19, Typography as Typography17, Paper as Paper3 } from "@mui/material";
5892
+ import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
5562
5893
  var SuccessDisplay = ({ data }) => {
5563
- return /* @__PURE__ */ jsxs23(Paper3, { elevation: 2, sx: { p: 2, mt: 2, bgcolor: "success.light", color: "success.contrastText" }, children: [
5564
- /* @__PURE__ */ jsx30(Typography16, { variant: "h6", sx: { fontSize: "1rem", mb: 1 }, children: "\xA1Operaci\xF3n Exitosa!" }),
5565
- /* @__PURE__ */ jsx30(Typography16, { variant: "body2", sx: { fontSize: "0.85rem" }, children: "Detalles del resultado:" }),
5566
- /* @__PURE__ */ jsx30(Box18, { sx: { bgcolor: "success.dark", p: 1, borderRadius: 1, mt: 1, overflowX: "auto" }, children: /* @__PURE__ */ jsx30("pre", { style: { margin: 0, fontSize: "0.75rem", color: "white" }, children: JSON.stringify(data, null, 2) }) }),
5567
- /* @__PURE__ */ jsx30(Typography16, { variant: "body2", sx: { fontSize: "0.8rem", mt: 1 }, children: "Puedes revisar los detalles anteriores o cerrar este mensaje." })
5894
+ return /* @__PURE__ */ jsxs24(Paper3, { elevation: 2, sx: { p: 2, mt: 2, bgcolor: "success.light", color: "success.contrastText" }, children: [
5895
+ /* @__PURE__ */ jsx31(Typography17, { variant: "h6", sx: { fontSize: "1rem", mb: 1 }, children: "\xA1Operaci\xF3n Exitosa!" }),
5896
+ /* @__PURE__ */ jsx31(Typography17, { variant: "body2", sx: { fontSize: "0.85rem" }, children: "Detalles del resultado:" }),
5897
+ /* @__PURE__ */ jsx31(Box19, { sx: { bgcolor: "success.dark", p: 1, borderRadius: 1, mt: 1, overflowX: "auto" }, children: /* @__PURE__ */ jsx31("pre", { style: { margin: 0, fontSize: "0.75rem", color: "white" }, children: JSON.stringify(data, null, 2) }) }),
5898
+ /* @__PURE__ */ jsx31(Typography17, { variant: "body2", sx: { fontSize: "0.8rem", mt: 1 }, children: "Puedes revisar los detalles anteriores o cerrar este mensaje." })
5568
5899
  ] });
5569
5900
  };
5570
5901
  var SuccessDisplay_default = SuccessDisplay;
@@ -5578,22 +5909,22 @@ var extendResultsOk = (newComponents) => {
5578
5909
  };
5579
5910
 
5580
5911
  // src/components/ProcedureForm.tsx
5581
- import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
5912
+ import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
5582
5913
  var ProcedureForm = () => {
5583
5914
  const { procedureName } = useParams();
5584
5915
  const { clientContext } = useApp();
5585
5916
  const { callApi, loading: apiLoading, error: apiError } = useApiCall();
5586
- const [validationErrors, setValidationErrors] = useState19({});
5587
- const [successMessage, setSuccessMessage] = useState19(null);
5588
- const [executionLog, setExecutionLog] = useState19([]);
5589
- const [procedureResultData, setProcedureResultData] = useState19(null);
5590
- const procedure = useMemo12(() => {
5917
+ const [validationErrors, setValidationErrors] = useState20({});
5918
+ const [successMessage, setSuccessMessage] = useState20(null);
5919
+ const [executionLog, setExecutionLog] = useState20([]);
5920
+ const [procedureResultData, setProcedureResultData] = useState20(null);
5921
+ const procedure = useMemo13(() => {
5591
5922
  if (procedureName && clientContext?.procedure) {
5592
5923
  return clientContext.procedure[procedureName];
5593
5924
  }
5594
5925
  return void 0;
5595
5926
  }, [procedureName, clientContext]);
5596
- const [formValues, setFormValues] = useState19(() => {
5927
+ const [formValues, setFormValues] = useState20(() => {
5597
5928
  if (!procedure) return {};
5598
5929
  const initialValues = {};
5599
5930
  procedure.parameters.forEach((param) => {
@@ -5793,9 +6124,9 @@ var ProcedureForm = () => {
5793
6124
  sx: { fontSize: "0.8rem" }
5794
6125
  };
5795
6126
  if (param.options && param.options.length > 0) {
5796
- return /* @__PURE__ */ jsxs24(FormControl3, { fullWidth: true, margin: "dense", size: "small", error: hasError, required: commonProps.required, children: [
5797
- /* @__PURE__ */ jsx31(InputLabel2, { id: `${param.name}-label`, sx: { fontSize: "0.8rem" }, children: param.label || param.name }),
5798
- /* @__PURE__ */ jsxs24(
6127
+ return /* @__PURE__ */ jsxs25(FormControl3, { fullWidth: true, margin: "dense", size: "small", error: hasError, required: commonProps.required, children: [
6128
+ /* @__PURE__ */ jsx32(InputLabel2, { id: `${param.name}-label`, sx: { fontSize: "0.8rem" }, children: param.label || param.name }),
6129
+ /* @__PURE__ */ jsxs25(
5799
6130
  Select2,
5800
6131
  {
5801
6132
  labelId: `${param.name}-label`,
@@ -5806,14 +6137,14 @@ var ProcedureForm = () => {
5806
6137
  label: param.label || param.name,
5807
6138
  disabled: apiLoading,
5808
6139
  sx: { fontSize: "0.8rem" },
5809
- renderValue: (selected) => /* @__PURE__ */ jsx31(Typography17, { sx: { fontSize: "0.8rem" }, children: selected }),
6140
+ renderValue: (selected) => /* @__PURE__ */ jsx32(Typography18, { sx: { fontSize: "0.8rem" }, children: selected }),
5810
6141
  children: [
5811
- (!commonProps.required || formValues[param.name] === "") && /* @__PURE__ */ jsx31(MenuItem2, { value: "", sx: { fontSize: "0.8rem" }, children: /* @__PURE__ */ jsx31("em", { children: "Seleccione..." }) }),
5812
- param.options.map((option) => /* @__PURE__ */ jsx31(MenuItem2, { value: option, sx: { fontSize: "0.8rem" }, children: option }, option))
6142
+ (!commonProps.required || formValues[param.name] === "") && /* @__PURE__ */ jsx32(MenuItem2, { value: "", sx: { fontSize: "0.8rem" }, children: /* @__PURE__ */ jsx32("em", { children: "Seleccione..." }) }),
6143
+ param.options.map((option) => /* @__PURE__ */ jsx32(MenuItem2, { value: option, sx: { fontSize: "0.8rem" }, children: option }, option))
5813
6144
  ]
5814
6145
  }
5815
6146
  ),
5816
- (param.description || errorMessage) && /* @__PURE__ */ jsx31(FormHelperText2, { sx: { fontSize: "0.7rem" }, children: errorMessage || param.description })
6147
+ (param.description || errorMessage) && /* @__PURE__ */ jsx32(FormHelperText2, { sx: { fontSize: "0.7rem" }, children: errorMessage || param.description })
5817
6148
  ] });
5818
6149
  }
5819
6150
  switch (param.typeName) {
@@ -5822,18 +6153,18 @@ var ProcedureForm = () => {
5822
6153
  case "tsrange":
5823
6154
  case "time_range":
5824
6155
  case "daterange":
5825
- return /* @__PURE__ */ jsx31(TextField3, { type: "text", value: formValues[param.name] || "", ...commonProps });
6156
+ return /* @__PURE__ */ jsx32(TextField4, { type: "text", value: formValues[param.name] || "", ...commonProps });
5826
6157
  case "decimal":
5827
6158
  case "double":
5828
- return /* @__PURE__ */ jsx31(TextField3, { type: "number", inputProps: { step: "any", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
6159
+ return /* @__PURE__ */ jsx32(TextField4, { type: "number", inputProps: { step: "any", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
5829
6160
  case "integer":
5830
6161
  case "bigint":
5831
- return /* @__PURE__ */ jsx31(TextField3, { type: "number", inputProps: { step: "1", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
6162
+ return /* @__PURE__ */ jsx32(TextField4, { type: "number", inputProps: { step: "1", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
5832
6163
  case "boolean":
5833
- return /* @__PURE__ */ jsx31(
6164
+ return /* @__PURE__ */ jsx32(
5834
6165
  FormControlLabel3,
5835
6166
  {
5836
- control: /* @__PURE__ */ jsx31(
6167
+ control: /* @__PURE__ */ jsx32(
5837
6168
  Checkbox3,
5838
6169
  {
5839
6170
  checked: !!formValues[param.name],
@@ -5844,21 +6175,21 @@ var ProcedureForm = () => {
5844
6175
  size: "small"
5845
6176
  }
5846
6177
  ),
5847
- label: /* @__PURE__ */ jsx31(Typography17, { sx: { fontSize: "0.8rem", color: hasError ? "error.main" : "inherit" }, children: param.label || param.name }),
6178
+ label: /* @__PURE__ */ jsx32(Typography18, { sx: { fontSize: "0.8rem", color: hasError ? "error.main" : "inherit" }, children: param.label || param.name }),
5848
6179
  sx: { mt: 1, mb: 0 }
5849
6180
  }
5850
6181
  );
5851
6182
  case "date":
5852
6183
  case "timestamp":
5853
6184
  case "time":
5854
- return /* @__PURE__ */ jsx31(TextField3, { type: param.typeName === "date" ? "date" : param.typeName === "timestamp" ? "datetime-local" : "time", value: formValues[param.name] || "", ...commonProps, InputLabelProps: { shrink: true, sx: { fontSize: "0.8rem" } } });
6185
+ return /* @__PURE__ */ jsx32(TextField4, { type: param.typeName === "date" ? "date" : param.typeName === "timestamp" ? "datetime-local" : "time", value: formValues[param.name] || "", ...commonProps, InputLabelProps: { shrink: true, sx: { fontSize: "0.8rem" } } });
5855
6186
  case "jsonb":
5856
6187
  case "jsona":
5857
- return /* @__PURE__ */ jsx31(TextField3, { multiline: true, rows: 5, value: formValues[param.name] || "", ...commonProps, InputProps: { sx: { fontSize: "0.8rem" } } });
6188
+ return /* @__PURE__ */ jsx32(TextField4, { multiline: true, rows: 5, value: formValues[param.name] || "", ...commonProps, InputProps: { sx: { fontSize: "0.8rem" } } });
5858
6189
  case "bytea":
5859
- return /* @__PURE__ */ jsxs24(FormControl3, { fullWidth: true, margin: "dense", error: hasError, required: commonProps.required, children: [
5860
- /* @__PURE__ */ jsx31(Typography17, { variant: "body2", color: "textSecondary", component: "label", htmlFor: param.name, sx: { fontSize: "0.8rem", color: hasError ? "error.main" : "text.secondary" }, children: param.label || param.name }),
5861
- /* @__PURE__ */ jsx31(
6190
+ return /* @__PURE__ */ jsxs25(FormControl3, { fullWidth: true, margin: "dense", error: hasError, required: commonProps.required, children: [
6191
+ /* @__PURE__ */ jsx32(Typography18, { variant: "body2", color: "textSecondary", component: "label", htmlFor: param.name, sx: { fontSize: "0.8rem", color: hasError ? "error.main" : "text.secondary" }, children: param.label || param.name }),
6192
+ /* @__PURE__ */ jsx32(
5862
6193
  "input",
5863
6194
  {
5864
6195
  type: "file",
@@ -5872,34 +6203,34 @@ var ProcedureForm = () => {
5872
6203
  style: { padding: "6px 0", fontSize: "0.8rem" }
5873
6204
  }
5874
6205
  ),
5875
- (param.description || errorMessage) && /* @__PURE__ */ jsx31(FormHelperText2, { sx: { fontSize: "0.7rem" }, children: errorMessage || param.description })
6206
+ (param.description || errorMessage) && /* @__PURE__ */ jsx32(FormHelperText2, { sx: { fontSize: "0.7rem" }, children: errorMessage || param.description })
5876
6207
  ] });
5877
6208
  default:
5878
- return /* @__PURE__ */ jsx31(TextField3, { type: "text", value: formValues[param.name] || "", ...commonProps });
6209
+ return /* @__PURE__ */ jsx32(TextField4, { type: "text", value: formValues[param.name] || "", ...commonProps });
5879
6210
  }
5880
6211
  }, [formValues, handleChange, apiLoading, validationErrors]);
5881
- const ResultDisplayContent = useMemo12(() => {
6212
+ const ResultDisplayContent = useMemo13(() => {
5882
6213
  if (!procedureResultData || !procedure?.resultOk) return null;
5883
6214
  const ComponentToRender = resultsOk[procedure.resultOk];
5884
6215
  if (ComponentToRender) {
5885
- return /* @__PURE__ */ jsx31(ComponentToRender, { data: procedureResultData });
6216
+ return /* @__PURE__ */ jsx32(ComponentToRender, { data: procedureResultData });
5886
6217
  } else {
5887
- return /* @__PURE__ */ jsxs24(Paper4, { elevation: 2, sx: { p: 2, mt: 0, bgcolor: "warning.light" }, children: [
5888
- /* @__PURE__ */ jsxs24(Typography17, { variant: "h6", sx: { fontSize: "1rem", mb: 1 }, children: [
6218
+ return /* @__PURE__ */ jsxs25(Paper4, { elevation: 2, sx: { p: 2, mt: 0, bgcolor: "warning.light" }, children: [
6219
+ /* @__PURE__ */ jsxs25(Typography18, { variant: "h6", sx: { fontSize: "1rem", mb: 1 }, children: [
5889
6220
  "el componente para resolver el resultOk: '",
5890
6221
  procedure.resultOk,
5891
6222
  "' definido en el procedure no existe o no est\xE1 mapeado."
5892
6223
  ] }),
5893
- /* @__PURE__ */ jsx31(Box19, { sx: {
6224
+ /* @__PURE__ */ jsx32(Box20, { sx: {
5894
6225
  bgcolor: "warning.dark",
5895
6226
  p: 1,
5896
6227
  borderRadius: 1
5897
- }, children: /* @__PURE__ */ jsx31("pre", { style: { margin: 0, fontSize: "0.75rem", color: "white" }, children: JSON.stringify(procedureResultData, null, 2) }) })
6228
+ }, children: /* @__PURE__ */ jsx32("pre", { style: { margin: 0, fontSize: "0.75rem", color: "white" }, children: JSON.stringify(procedureResultData, null, 2) }) })
5898
6229
  ] });
5899
6230
  }
5900
6231
  }, [procedureResultData, procedure]);
5901
6232
  if (!procedure) {
5902
- return /* @__PURE__ */ jsxs24(Box19, { sx: {
6233
+ return /* @__PURE__ */ jsxs25(Box20, { sx: {
5903
6234
  p: 2,
5904
6235
  width: "100%",
5905
6236
  boxSizing: "border-box",
@@ -5909,13 +6240,13 @@ var ProcedureForm = () => {
5909
6240
  boxShadow: 3,
5910
6241
  textAlign: "center"
5911
6242
  }, children: [
5912
- /* @__PURE__ */ jsx31(CircularProgress7, { sx: { mb: 2 } }),
5913
- /* @__PURE__ */ jsx31(Typography17, { variant: "h6", color: "text.secondary", children: "Cargando procedimiento o no encontrado..." })
6243
+ /* @__PURE__ */ jsx32(CircularProgress7, { sx: { mb: 2 } }),
6244
+ /* @__PURE__ */ jsx32(Typography18, { variant: "h6", color: "text.secondary", children: "Cargando procedimiento o no encontrado..." })
5914
6245
  ] });
5915
6246
  }
5916
6247
  return (
5917
6248
  // Esta es la Box más externa que ahora controlará el scroll
5918
- /* @__PURE__ */ jsxs24(Box19, { sx: {
6249
+ /* @__PURE__ */ jsxs25(Box20, { sx: {
5919
6250
  p: 2,
5920
6251
  ml: 1,
5921
6252
  mt: 2,
@@ -5932,16 +6263,16 @@ var ProcedureForm = () => {
5932
6263
  overflowX: "hidden"
5933
6264
  // Oculta el scroll horizontal si no es necesario para el contenedor principal
5934
6265
  }, children: [
5935
- /* @__PURE__ */ jsx31(Typography17, { variant: "h5", component: "h2", gutterBottom: true, align: "left", children: cambiarGuionesBajosPorEspacios(procedure.proceedLabel || procedure.action) }),
5936
- Object.values(validationErrors).some((err) => err !== null) && /* @__PURE__ */ jsxs24(Alert4, { severity: "error", sx: { mb: 2, fontSize: "0.8rem" }, children: [
6266
+ /* @__PURE__ */ jsx32(Typography18, { variant: "h5", component: "h2", gutterBottom: true, align: "left", children: cambiarGuionesBajosPorEspacios(procedure.proceedLabel || procedure.action) }),
6267
+ Object.values(validationErrors).some((err) => err !== null) && /* @__PURE__ */ jsxs25(Alert4, { severity: "error", sx: { mb: 2, fontSize: "0.8rem" }, children: [
5937
6268
  "Por favor, corrige los errores en el formulario antes de enviar.",
5938
- /* @__PURE__ */ jsx31("ul", { children: Object.values(validationErrors).map((err, idx) => err && /* @__PURE__ */ jsx31("li", { children: err }, idx)) })
6269
+ /* @__PURE__ */ jsx32("ul", { children: Object.values(validationErrors).map((err, idx) => err && /* @__PURE__ */ jsx32("li", { children: err }, idx)) })
5939
6270
  ] }),
5940
- !Object.values(validationErrors).some((err) => err !== null) && apiError && /* @__PURE__ */ jsx31(Alert4, { severity: "error", sx: { mb: 2, fontSize: "0.8rem" }, children: apiError.message }),
5941
- !Object.values(validationErrors).some((err) => err !== null) && !apiError && successMessage && /* @__PURE__ */ jsx31(Alert4, { severity: "success", sx: { mb: 2, fontSize: "0.8rem" }, children: successMessage }),
5942
- /* @__PURE__ */ jsxs24("form", { onSubmit: handleSubmit, children: [
5943
- procedure.parameters.map((param) => /* @__PURE__ */ jsx31("div", { children: renderInputField(param) }, param.name)),
5944
- /* @__PURE__ */ jsx31(
6271
+ !Object.values(validationErrors).some((err) => err !== null) && apiError && /* @__PURE__ */ jsx32(Alert4, { severity: "error", sx: { mb: 2, fontSize: "0.8rem" }, children: apiError.message }),
6272
+ !Object.values(validationErrors).some((err) => err !== null) && !apiError && successMessage && /* @__PURE__ */ jsx32(Alert4, { severity: "success", sx: { mb: 2, fontSize: "0.8rem" }, children: successMessage }),
6273
+ /* @__PURE__ */ jsxs25("form", { onSubmit: handleSubmit, children: [
6274
+ procedure.parameters.map((param) => /* @__PURE__ */ jsx32("div", { children: renderInputField(param) }, param.name)),
6275
+ /* @__PURE__ */ jsx32(
5945
6276
  Button11,
5946
6277
  {
5947
6278
  type: "submit",
@@ -5950,20 +6281,20 @@ var ProcedureForm = () => {
5950
6281
  disabled: apiLoading || Object.values(validationErrors).some((err) => err !== null),
5951
6282
  sx: { mt: 2 },
5952
6283
  size: "medium",
5953
- startIcon: apiLoading ? /* @__PURE__ */ jsx31(CircularProgress7, { size: 20, color: "inherit" }) : null,
6284
+ startIcon: apiLoading ? /* @__PURE__ */ jsx32(CircularProgress7, { size: 20, color: "inherit" }) : null,
5954
6285
  children: apiLoading ? "Ejecutando..." : procedure.proceedLabel || "Ejecutar"
5955
6286
  }
5956
6287
  )
5957
6288
  ] }),
5958
- ResultDisplayContent && /* @__PURE__ */ jsxs24(Box19, { sx: { mt: 2 }, children: [
6289
+ ResultDisplayContent && /* @__PURE__ */ jsxs25(Box20, { sx: { mt: 2 }, children: [
5959
6290
  " ",
5960
6291
  ResultDisplayContent
5961
6292
  ] }),
5962
- executionLog.length > 0 && /* @__PURE__ */ jsxs24(Box19, { sx: { mt: 3 }, children: [
5963
- /* @__PURE__ */ jsx31(Divider3, { sx: { mb: 2 } }),
5964
- /* @__PURE__ */ jsx31(Typography17, { variant: "h6", gutterBottom: true, sx: { fontSize: "0.9rem" }, children: "Registro de Ejecuci\xF3n" }),
5965
- /* @__PURE__ */ jsx31(
5966
- Box19,
6293
+ executionLog.length > 0 && /* @__PURE__ */ jsxs25(Box20, { sx: { mt: 3 }, children: [
6294
+ /* @__PURE__ */ jsx32(Divider3, { sx: { mb: 2 } }),
6295
+ /* @__PURE__ */ jsx32(Typography18, { variant: "h6", gutterBottom: true, sx: { fontSize: "0.9rem" }, children: "Registro de Ejecuci\xF3n" }),
6296
+ /* @__PURE__ */ jsx32(
6297
+ Box20,
5967
6298
  {
5968
6299
  sx: {
5969
6300
  bgcolor: "grey.100",
@@ -5976,8 +6307,8 @@ var ProcedureForm = () => {
5976
6307
  fontSize: "0.75rem",
5977
6308
  whiteSpace: "pre-wrap"
5978
6309
  },
5979
- children: executionLog.map((log, index) => /* @__PURE__ */ jsx31(
5980
- Typography17,
6310
+ children: executionLog.map((log, index) => /* @__PURE__ */ jsx32(
6311
+ Typography18,
5981
6312
  {
5982
6313
  component: "pre",
5983
6314
  sx: {
@@ -6003,10 +6334,10 @@ import { PersistGate } from "redux-persist/integration/react";
6003
6334
 
6004
6335
  // src/components/PrivateRoute.tsx
6005
6336
  import { useEffect as useEffect11 } from "react";
6006
- import { Navigate as Navigate2, useLocation as useLocation3 } from "react-router-dom";
6007
- import { Fragment as Fragment6, jsx as jsx32 } from "react/jsx-runtime";
6337
+ import { Navigate as Navigate2, useLocation as useLocation4 } from "react-router-dom";
6338
+ import { Fragment as Fragment7, jsx as jsx33 } from "react/jsx-runtime";
6008
6339
  var PrivateRoute = ({ children }) => {
6009
- const location = useLocation3();
6340
+ const location = useLocation4();
6010
6341
  const dispatch = useAppDispatch();
6011
6342
  const clientContextStatus = useClientContextStatus();
6012
6343
  const isAuthenticated = clientContextStatus === "succeeded";
@@ -6014,7 +6345,7 @@ var PrivateRoute = ({ children }) => {
6014
6345
  console.log(`[PrivateRoute] Render: Path=${location.pathname}, AuthStatus=${clientContextStatus}, IsAuthenticated=${isAuthenticated}, IsLoadingAuth=${isLoadingAuth}`);
6015
6346
  useEffect11(() => {
6016
6347
  console.log(`[PrivateRoute] Effect: Path=${location.pathname}, AuthStatus=${clientContextStatus}, IsAuthenticated=${isAuthenticated}, IsLoadingAuth=${isLoadingAuth}`);
6017
- if (!isAuthenticated && !isLoadingAuth && location.pathname !== "/login") {
6348
+ if (!isAuthenticated && !isLoadingAuth && location.pathname !== "/login" && !huboCierreVoluntario()) {
6018
6349
  const path = location.pathname + location.search + location.hash;
6019
6350
  console.log(`[PrivateRoute] ACTION: Dispatching setRedirectPath(${path})`);
6020
6351
  dispatch(setRedirectPath(path));
@@ -6028,36 +6359,40 @@ var PrivateRoute = ({ children }) => {
6028
6359
  }, [isAuthenticated, isLoadingAuth, location, dispatch, clientContextStatus]);
6029
6360
  if (isLoadingAuth) {
6030
6361
  console.log(`[PrivateRoute] Returning loading state...`);
6031
- return /* @__PURE__ */ jsx32("p", { children: "Verificando autenticaci\xF3n..." });
6362
+ return /* @__PURE__ */ jsx33("p", { children: "Verificando autenticaci\xF3n..." });
6032
6363
  }
6033
6364
  if (!isAuthenticated) {
6034
6365
  console.log(`[PrivateRoute] User not authenticated, navigating to /login`);
6035
- return /* @__PURE__ */ jsx32(Navigate2, { to: "/login", replace: true });
6366
+ return /* @__PURE__ */ jsx33(Navigate2, { to: "/login", replace: true });
6036
6367
  }
6037
6368
  console.log(`[PrivateRoute] User authenticated, rendering children.`);
6038
- return /* @__PURE__ */ jsx32(Fragment6, { children });
6369
+ return /* @__PURE__ */ jsx33(Fragment7, { children });
6039
6370
  };
6040
6371
  var PrivateRoute_default = PrivateRoute;
6041
6372
 
6042
6373
  // src/components/InitialRedirectHandler.tsx
6043
- import { useEffect as useEffect12, useRef as useRef9 } from "react";
6044
- import { useLocation as useLocation4, useNavigate as useNavigate4 } from "react-router-dom";
6374
+ import { useEffect as useEffect12, useRef as useRef10 } from "react";
6375
+ import { useLocation as useLocation5, useNavigate as useNavigate5 } from "react-router-dom";
6045
6376
  var InitialRedirectHandler = () => {
6046
- const location = useLocation4();
6047
- const navigate = useNavigate4();
6377
+ const location = useLocation5();
6378
+ const navigate = useNavigate5();
6048
6379
  const dispatch = useAppDispatch();
6049
6380
  const redirectPath = useRedirectPath();
6050
6381
  const clientContextStatus = useClientContextStatus();
6382
+ const username = useAppSelector((state) => state.clientContext.username);
6383
+ const redirectUsername = useAppSelector((state) => state.router.redirectUsername);
6051
6384
  useEffect12(() => {
6052
6385
  dispatch(setCurrentPath(location.pathname + location.search));
6053
6386
  }, [location, dispatch]);
6054
- const navegandoRef = useRef9(false);
6387
+ const navegandoRef = useRef10(false);
6055
6388
  useEffect12(() => {
6056
6389
  navegandoRef.current = false;
6057
6390
  }, [location.pathname]);
6058
6391
  useEffect12(() => {
6059
6392
  if (clientContextStatus === "succeeded") {
6060
- if (redirectPath && redirectPath !== "/login") {
6393
+ if (redirectPath && redirectPath !== "/login" && redirectUsername && redirectUsername !== username) {
6394
+ dispatch(setRedirectPath(null));
6395
+ } else if (redirectPath && redirectPath !== "/login") {
6061
6396
  console.log("Redirigiendo a la ruta guardada:", redirectPath);
6062
6397
  navegandoRef.current = true;
6063
6398
  navigate(redirectPath);
@@ -6067,17 +6402,17 @@ var InitialRedirectHandler = () => {
6067
6402
  navigate("/");
6068
6403
  }
6069
6404
  }
6070
- }, [clientContextStatus, redirectPath, location.pathname, navigate, dispatch]);
6405
+ }, [clientContextStatus, redirectPath, redirectUsername, username, location.pathname, navigate, dispatch]);
6071
6406
  return null;
6072
6407
  };
6073
6408
  var InitialRedirectHandler_default = InitialRedirectHandler;
6074
6409
 
6075
6410
  // src/pages/GenericDataGridPage.tsx
6076
- import React26, { useState as useState20, useCallback as useCallback16, useMemo as useMemo13 } from "react";
6077
- import { useParams as useParams2, useLocation as useLocation5 } from "react-router-dom";
6078
- import { Box as Box20, Alert as Alert5, Typography as Typography18, Tabs, Tab, IconButton as IconButton7 } from "@mui/material";
6411
+ import React26, { useState as useState21, useCallback as useCallback16, useMemo as useMemo14 } from "react";
6412
+ import { useParams as useParams2, useLocation as useLocation6 } from "react-router-dom";
6413
+ import { Box as Box21, Alert as Alert5, Typography as Typography19, Tabs, Tab, IconButton as IconButton9 } from "@mui/material";
6079
6414
  import CloseIcon2 from "@mui/icons-material/Close";
6080
- import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
6415
+ import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
6081
6416
  var GenericDataGridPage = ({
6082
6417
  initialTableName,
6083
6418
  initialFixedFields = [],
@@ -6085,11 +6420,11 @@ var GenericDataGridPage = ({
6085
6420
  }) => {
6086
6421
  const { tableName: urlTableName } = useParams2();
6087
6422
  const effectiveTableName = initialTableName || urlTableName;
6088
- const [tabs, setTabs] = useState20([]);
6089
- const [activeTabIdx, setActiveTabIdx] = useState20(0);
6423
+ const [tabs, setTabs] = useState21([]);
6424
+ const [activeTabIdx, setActiveTabIdx] = useState21(0);
6090
6425
  const ffKey = JSON.stringify(initialFixedFields);
6091
- const location = useLocation5();
6092
- const parametros = useMemo13(() => parametrosRecibidos ?? parametrosDeTabla(void 0, location.search), [parametrosRecibidos, location.search]);
6426
+ const location = useLocation6();
6427
+ const parametros = useMemo14(() => parametrosRecibidos ?? parametrosDeTabla(void 0, location.search), [parametrosRecibidos, location.search]);
6093
6428
  const fixedFieldsIniciales = initialFixedFields.length > 0 ? initialFixedFields : parametros.fixedFields ?? [];
6094
6429
  const parametrosKey = JSON.stringify(parametros);
6095
6430
  React26.useEffect(() => {
@@ -6128,26 +6463,26 @@ var GenericDataGridPage = ({
6128
6463
  else if (activeTabIdx === index) setActiveTabIdx(index - 1);
6129
6464
  };
6130
6465
  if (!effectiveTableName) {
6131
- return /* @__PURE__ */ jsx33(Box20, { p: 3, children: /* @__PURE__ */ jsx33(Alert5, { severity: "error", children: "No se especific\xF3 una tabla." }) });
6466
+ return /* @__PURE__ */ jsx34(Box21, { p: 3, children: /* @__PURE__ */ jsx34(Alert5, { severity: "error", children: "No se especific\xF3 una tabla." }) });
6132
6467
  }
6133
- return /* @__PURE__ */ jsxs25(Box20, { sx: { width: "100%", height: "calc(100vh - 64px)", display: "flex", flexDirection: "column" }, children: [
6134
- /* @__PURE__ */ jsx33(Box20, { sx: { borderBottom: 1, borderColor: "divider", bgcolor: "background.paper" }, children: /* @__PURE__ */ jsx33(Tabs, { value: activeTabIdx, onChange: (_, v) => setActiveTabIdx(v), variant: "scrollable", children: tabs.map((tab, index) => /* @__PURE__ */ jsx33(
6468
+ return /* @__PURE__ */ jsxs26(Box21, { sx: { width: "100%", height: "calc(100vh - 64px)", display: "flex", flexDirection: "column" }, children: [
6469
+ /* @__PURE__ */ jsx34(Box21, { sx: { borderBottom: 1, borderColor: "divider", bgcolor: "background.paper" }, children: /* @__PURE__ */ jsx34(Tabs, { value: activeTabIdx, onChange: (_, v) => setActiveTabIdx(v), variant: "scrollable", children: tabs.map((tab, index) => /* @__PURE__ */ jsx34(
6135
6470
  Tab,
6136
6471
  {
6137
- label: /* @__PURE__ */ jsxs25(Box20, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
6138
- /* @__PURE__ */ jsx33(Typography18, { variant: "caption", sx: { fontWeight: "bold" }, children: tab.label }),
6139
- index !== 0 && /* @__PURE__ */ jsx33(IconButton7, { size: "small", onClick: (e) => handleCloseTab(index, e), children: /* @__PURE__ */ jsx33(CloseIcon2, { fontSize: "inherit" }) })
6472
+ label: /* @__PURE__ */ jsxs26(Box21, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
6473
+ /* @__PURE__ */ jsx34(Typography19, { variant: "caption", sx: { fontWeight: "bold" }, children: tab.label }),
6474
+ index !== 0 && /* @__PURE__ */ jsx34(IconButton9, { size: "small", onClick: (e) => handleCloseTab(index, e), children: /* @__PURE__ */ jsx34(CloseIcon2, { fontSize: "inherit" }) })
6140
6475
  ] })
6141
6476
  },
6142
6477
  tab.id
6143
6478
  )) }) }),
6144
- tabs.map((tab, index) => /* @__PURE__ */ jsx33(
6145
- Box20,
6479
+ tabs.map((tab, index) => /* @__PURE__ */ jsx34(
6480
+ Box21,
6146
6481
  {
6147
6482
  role: "tabpanel",
6148
6483
  hidden: activeTabIdx !== index,
6149
6484
  sx: { flexGrow: 1, display: activeTabIdx === index ? "flex" : "none", flexDirection: "column", overflow: "hidden" },
6150
- children: /* @__PURE__ */ jsx33(
6485
+ children: /* @__PURE__ */ jsx34(
6151
6486
  GenericDataGrid_default,
6152
6487
  {
6153
6488
  tableName: tab.tableName,
@@ -6168,44 +6503,32 @@ var GenericDataGridPage = ({
6168
6503
  var GenericDataGridPage_default = GenericDataGridPage;
6169
6504
 
6170
6505
  // src/pages/PantallaDeRuta.tsx
6171
- import { useMemo as useMemo14 } from "react";
6506
+ import { useMemo as useMemo15 } from "react";
6172
6507
  import { useSearchParams } from "react-router-dom";
6173
- import { Box as Box21 } from "@mui/material";
6174
- import { jsx as jsx34 } from "react/jsx-runtime";
6508
+ import { Box as Box22 } from "@mui/material";
6509
+ import { jsx as jsx35 } from "react/jsx-runtime";
6175
6510
  var PantallaDeRuta = ({ screenName }) => {
6176
6511
  const [busqueda] = useSearchParams();
6177
- const params = useMemo14(() => Object.fromEntries(busqueda.entries()), [busqueda]);
6512
+ const params = useMemo15(() => Object.fromEntries(busqueda.entries()), [busqueda]);
6178
6513
  const Pantalla = wScreens[screenName];
6179
- return /* @__PURE__ */ jsx34(Box21, { sx: { pt: 3, pl: 4 }, children: Pantalla ? /* @__PURE__ */ jsx34(Pantalla, { screenName, params }) : /* @__PURE__ */ jsx34(FallbackWScreen, { screenName }) });
6514
+ return /* @__PURE__ */ jsx35(Box22, { sx: { pt: 3, pl: 4 }, children: Pantalla ? /* @__PURE__ */ jsx35(Pantalla, { screenName, params }) : /* @__PURE__ */ jsx35(FallbackWScreen, { screenName }) });
6180
6515
  };
6181
6516
 
6182
6517
  // src/pages/MenuTablePage.tsx
6183
- import { useMemo as useMemo15 } from "react";
6184
- import { useLocation as useLocation6, useParams as useParams3 } from "react-router-dom";
6185
- import { Box as Box22, Alert as Alert6, Typography as Typography19 } from "@mui/material";
6186
- import { jsx as jsx35, jsxs as jsxs26 } from "react/jsx-runtime";
6518
+ import { useMemo as useMemo16 } from "react";
6519
+ import { useLocation as useLocation7, useParams as useParams3 } from "react-router-dom";
6520
+ import { Box as Box23, Alert as Alert6, Typography as Typography20 } from "@mui/material";
6521
+ import { jsx as jsx36, jsxs as jsxs27 } from "react/jsx-runtime";
6187
6522
  var MenuTablePage = () => {
6188
- const { menuName } = useParams3();
6523
+ const { "*": menuName } = useParams3();
6189
6524
  const { clientContext } = useApp();
6190
- const { search } = useLocation6();
6191
- const findMenuItem = (items, nameToFind) => {
6192
- for (const item of items) {
6193
- if (item.name === nameToFind && item.menuType === "table") {
6194
- return item;
6195
- }
6196
- if (item.menuType === "menu" && item.menuContent) {
6197
- const found = findMenuItem(item.menuContent, nameToFind);
6198
- if (found) return found;
6199
- }
6200
- }
6201
- return void 0;
6202
- };
6203
- const { currentTableName, currentFixedFields, parametros } = useMemo15(() => {
6525
+ const { search } = useLocation7();
6526
+ const { currentTableName, currentFixedFields, parametros } = useMemo16(() => {
6204
6527
  let tableName = void 0;
6205
6528
  let fixedFields = [];
6206
6529
  let parametros2 = {};
6207
6530
  if (menuName && clientContext && clientContext.menu) {
6208
- const menuItem = findMenuItem(clientContext.menu, menuName);
6531
+ const menuItem = itemDeRuta(clientContext.menu, menuName.split("/").filter((tramo) => tramo !== ""));
6209
6532
  if (menuItem) {
6210
6533
  tableName = menuItem.table || menuItem.name;
6211
6534
  parametros2 = parametrosDeTabla(menuItem, search);
@@ -6221,12 +6544,12 @@ var MenuTablePage = () => {
6221
6544
  };
6222
6545
  }, [menuName, clientContext, search]);
6223
6546
  if (!currentTableName) {
6224
- return /* @__PURE__ */ jsx35(Box22, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: /* @__PURE__ */ jsxs26(Alert6, { severity: "error", children: [
6225
- /* @__PURE__ */ jsx35(Typography19, { variant: "h6", children: "Error: No se pudo cargar la informaci\xF3n de la tabla." }),
6226
- /* @__PURE__ */ jsx35(Typography19, { variant: "body1", children: "Verifique la configuraci\xF3n del men\xFA o el nombre de la tabla en la URL." })
6547
+ return /* @__PURE__ */ jsx36(Box23, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "200px", children: /* @__PURE__ */ jsxs27(Alert6, { severity: "error", children: [
6548
+ /* @__PURE__ */ jsx36(Typography20, { variant: "h6", children: "Error: No se pudo cargar la informaci\xF3n de la tabla." }),
6549
+ /* @__PURE__ */ jsx36(Typography20, { variant: "body1", children: "Verifique la configuraci\xF3n del men\xFA o el nombre de la tabla en la URL." })
6227
6550
  ] }) });
6228
6551
  }
6229
- return /* @__PURE__ */ jsx35(
6552
+ return /* @__PURE__ */ jsx36(
6230
6553
  GenericDataGridPage_default,
6231
6554
  {
6232
6555
  initialTableName: currentTableName,
@@ -6238,9 +6561,9 @@ var MenuTablePage = () => {
6238
6561
  var MenuTablePage_default = MenuTablePage;
6239
6562
 
6240
6563
  // src/puente/PantallaBP.tsx
6241
- import { useEffect as useEffect15, useRef as useRef10, useState as useState21 } from "react";
6242
- import { Navigate as Navigate3, useLocation as useLocation7, useParams as useParams4 } from "react-router-dom";
6243
- import { Alert as Alert7, Box as Box24, CircularProgress as CircularProgress8 } from "@mui/material";
6564
+ import { useEffect as useEffect15, useRef as useRef11, useState as useState22 } from "react";
6565
+ import { Navigate as Navigate3, useLocation as useLocation8, useParams as useParams4 } from "react-router-dom";
6566
+ import { Alert as Alert7, Box as Box25, CircularProgress as CircularProgress8 } from "@mui/material";
6244
6567
 
6245
6568
  // src/puente/clienteBP.ts
6246
6569
  function extraerRecursos(html) {
@@ -6330,8 +6653,8 @@ async function cargarSinMemoria(urlBackend) {
6330
6653
 
6331
6654
  // src/puente/ConsolaBP.tsx
6332
6655
  import { useEffect as useEffect14 } from "react";
6333
- import { Box as Box23 } from "@mui/material";
6334
- import { jsx as jsx36 } from "react/jsx-runtime";
6656
+ import { Box as Box24 } from "@mui/material";
6657
+ import { jsx as jsx37 } from "react/jsx-runtime";
6335
6658
  var MARGEN_DE_REPETIDO_MS = 1e3;
6336
6659
  function descartarRepetidos(consola, agregados, ahora) {
6337
6660
  for (const nodo of agregados) {
@@ -6353,8 +6676,8 @@ var ConsolaBP = () => {
6353
6676
  observador.observe(consola, { childList: true });
6354
6677
  return () => observador.disconnect();
6355
6678
  }, []);
6356
- return /* @__PURE__ */ jsx36(
6357
- Box23,
6679
+ return /* @__PURE__ */ jsx37(
6680
+ Box24,
6358
6681
  {
6359
6682
  id: "mini-console",
6360
6683
  sx: (tema) => ({
@@ -6442,15 +6765,8 @@ function resolverPantallaBP(myOwn, nombre, contenedor) {
6442
6765
  return null;
6443
6766
  }
6444
6767
 
6445
- // src/puente/habilitacion.ts
6446
- var habilitado = false;
6447
- var habilitarPuenteBP = () => {
6448
- habilitado = true;
6449
- };
6450
- var puenteBPHabilitado = () => habilitado;
6451
-
6452
6768
  // src/puente/PantallaBP.tsx
6453
- import { jsx as jsx37, jsxs as jsxs27 } from "react/jsx-runtime";
6769
+ import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
6454
6770
  function buscarItemDeMenu(items, tipo) {
6455
6771
  for (const item of items) {
6456
6772
  if (item.menuType === tipo) return item;
@@ -6463,10 +6779,10 @@ function buscarItemDeMenu(items, tipo) {
6463
6779
  }
6464
6780
  var PantallaBP = ({ nombre, direccion }) => {
6465
6781
  const { clientContext } = useApp();
6466
- const [cargando, setCargando] = useState21(true);
6467
- const [error, setError] = useState21(null);
6468
- const [noExiste, setNoExiste] = useState21(false);
6469
- const contenedor = useRef10(null);
6782
+ const [cargando, setCargando] = useState22(true);
6783
+ const [error, setError] = useState22(null);
6784
+ const [noExiste, setNoExiste] = useState22(false);
6785
+ const contenedor = useRef11(null);
6470
6786
  const menu = clientContext.menu;
6471
6787
  useEffect15(() => {
6472
6788
  const elemento = contenedor.current;
@@ -6499,47 +6815,47 @@ var PantallaBP = ({ nombre, direccion }) => {
6499
6815
  elemento.replaceChildren();
6500
6816
  };
6501
6817
  }, [nombre, direccion, menu]);
6502
- if (noExiste) return /* @__PURE__ */ jsx37(FallbackWScreen, { screenName: nombre });
6818
+ if (noExiste) return /* @__PURE__ */ jsx38(FallbackWScreen, { screenName: nombre });
6503
6819
  return (
6504
6820
  // el indicador de carga flota: si ocupara lugar, el contenido de backend-plus empezaría más abajo mientras carga y subiría al terminar
6505
- /* @__PURE__ */ jsxs27(Box24, { sx: { position: "relative" }, children: [
6506
- cargando && /* @__PURE__ */ jsx37(CircularProgress8, { size: 24, sx: { position: "absolute", top: 16, left: 16 } }),
6507
- error && /* @__PURE__ */ jsx37(Alert7, { severity: "error", sx: { m: 2 }, children: error }),
6508
- /* @__PURE__ */ jsx37("div", { id: "main_layout", ref: contenedor }),
6509
- /* @__PURE__ */ jsx37(ConsolaBP, {})
6821
+ /* @__PURE__ */ jsxs28(Box25, { sx: { position: "relative" }, children: [
6822
+ cargando && /* @__PURE__ */ jsx38(CircularProgress8, { size: 24, sx: { position: "absolute", top: 16, left: 16 } }),
6823
+ error && /* @__PURE__ */ jsx38(Alert7, { severity: "error", sx: { m: 2 }, children: error }),
6824
+ /* @__PURE__ */ jsx38("div", { id: "main_layout", ref: contenedor }),
6825
+ /* @__PURE__ */ jsx38(ConsolaBP, {})
6510
6826
  ] })
6511
6827
  );
6512
6828
  };
6513
6829
  var PantallaDeRespaldo = () => {
6514
6830
  const { screenName = "" } = useParams4();
6515
- const { search } = useLocation7();
6831
+ const { search } = useLocation8();
6516
6832
  if (!puenteBPHabilitado()) {
6517
- return /* @__PURE__ */ jsx37(Box24, { sx: { p: 3 }, children: /* @__PURE__ */ jsx37(FallbackWScreen, { screenName }) });
6833
+ return /* @__PURE__ */ jsx38(Box25, { sx: { p: 3 }, children: /* @__PURE__ */ jsx38(FallbackWScreen, { screenName }) });
6518
6834
  }
6519
- return /* @__PURE__ */ jsx37(Navigate3, { to: rutaDePantallaBP(screenName, search), replace: true });
6835
+ return /* @__PURE__ */ jsx38(Navigate3, { to: rutaDePantallaBP(screenName, search), replace: true });
6520
6836
  };
6521
6837
  var PaginaBP = () => {
6522
- const { hash, search } = useLocation7();
6838
+ const { hash, search } = useLocation8();
6523
6839
  const direccion = hash || search;
6524
6840
  const nombre = new URLSearchParams(direccion.replace(/^[#?]/, "")).get("w");
6525
- if (!puenteBPHabilitado()) return /* @__PURE__ */ jsx37(Navigate3, { to: "/", replace: true });
6526
- if (!nombre) return /* @__PURE__ */ jsx37(Box24, { sx: { p: 3 }, children: /* @__PURE__ */ jsxs27(Alert7, { severity: "warning", children: [
6841
+ if (!puenteBPHabilitado()) return /* @__PURE__ */ jsx38(Navigate3, { to: "/", replace: true });
6842
+ if (!nombre) return /* @__PURE__ */ jsx38(Box25, { sx: { p: 3 }, children: /* @__PURE__ */ jsxs28(Alert7, { severity: "warning", children: [
6527
6843
  "Falta la pantalla de backend-plus (",
6528
- /* @__PURE__ */ jsx37("code", { children: "#w=..." }),
6844
+ /* @__PURE__ */ jsx38("code", { children: "#w=..." }),
6529
6845
  ")."
6530
6846
  ] }) });
6531
- return /* @__PURE__ */ jsx37(PantallaBP, { nombre, direccion }, nombre);
6847
+ return /* @__PURE__ */ jsx38(PantallaBP, { nombre, direccion }, nombre);
6532
6848
  };
6533
6849
 
6534
6850
  // src/puente/GrillaBP.tsx
6535
- import { useEffect as useEffect16, useRef as useRef11, useState as useState22 } from "react";
6851
+ import { useEffect as useEffect16, useRef as useRef12, useState as useState23 } from "react";
6536
6852
  import { useParams as useParams5 } from "react-router-dom";
6537
- import { Alert as Alert8, Box as Box25, CircularProgress as CircularProgress9 } from "@mui/material";
6538
- import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
6853
+ import { Alert as Alert8, Box as Box26, CircularProgress as CircularProgress9 } from "@mui/material";
6854
+ import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
6539
6855
  var GrillaBP = ({ tabla, fixedFields }) => {
6540
- const contenedor = useRef11(null);
6541
- const [cargando, setCargando] = useState22(true);
6542
- const [error, setError] = useState22(null);
6856
+ const contenedor = useRef12(null);
6857
+ const [cargando, setCargando] = useState23(true);
6858
+ const [error, setError] = useState23(null);
6543
6859
  const clave = JSON.stringify(fixedFields ?? []);
6544
6860
  useEffect16(() => {
6545
6861
  const elemento = contenedor.current;
@@ -6564,22 +6880,22 @@ var GrillaBP = ({ tabla, fixedFields }) => {
6564
6880
  }, [tabla, clave]);
6565
6881
  return (
6566
6882
  // el indicador de carga flota: si ocupara lugar, el contenido de backend-plus empezaría más abajo mientras carga y subiría al terminar
6567
- /* @__PURE__ */ jsxs28(Box25, { sx: { position: "relative" }, children: [
6568
- cargando && /* @__PURE__ */ jsx38(CircularProgress9, { size: 24, sx: { position: "absolute", top: 16, left: 16 } }),
6569
- error && /* @__PURE__ */ jsx38(Alert8, { severity: "error", sx: { m: 2 }, children: error }),
6570
- /* @__PURE__ */ jsx38("div", { ref: contenedor }),
6571
- /* @__PURE__ */ jsx38(ConsolaBP, {})
6883
+ /* @__PURE__ */ jsxs29(Box26, { sx: { position: "relative" }, children: [
6884
+ cargando && /* @__PURE__ */ jsx39(CircularProgress9, { size: 24, sx: { position: "absolute", top: 16, left: 16 } }),
6885
+ error && /* @__PURE__ */ jsx39(Alert8, { severity: "error", sx: { m: 2 }, children: error }),
6886
+ /* @__PURE__ */ jsx39("div", { ref: contenedor }),
6887
+ /* @__PURE__ */ jsx39(ConsolaBP, {})
6572
6888
  ] })
6573
6889
  );
6574
6890
  };
6575
6891
  var PaginaGrillaBP = () => {
6576
6892
  const { tableName = "" } = useParams5();
6577
- return /* @__PURE__ */ jsx38(GrillaBP, { tabla: tableName }, tableName);
6893
+ return /* @__PURE__ */ jsx39(GrillaBP, { tabla: tableName }, tableName);
6578
6894
  };
6579
6895
 
6580
6896
  // src/puente/CapturaBP.tsx
6581
6897
  import { useEffect as useEffect17 } from "react";
6582
- import { useNavigate as useNavigate5 } from "react-router-dom";
6898
+ import { useNavigate as useNavigate6 } from "react-router-dom";
6583
6899
 
6584
6900
  // src/puente/capturaDeNavegacion.ts
6585
6901
  function apiDeNavegacion() {
@@ -6624,16 +6940,128 @@ function capturarNavegacionBP(urlBackend, navegar) {
6624
6940
 
6625
6941
  // src/puente/CapturaBP.tsx
6626
6942
  var CapturaBP = () => {
6627
- const navigate = useNavigate5();
6943
+ const navigate = useNavigate6();
6628
6944
  useEffect17(() => capturarNavegacionBP(envConfig.backendUrl, navigate), [navigate]);
6629
6945
  return null;
6630
6946
  };
6631
6947
 
6948
+ // src/pages/CambiarClavePage.tsx
6949
+ import { useState as useState24 } from "react";
6950
+ import { Alert as Alert9, Box as Box27, Button as Button12, CircularProgress as CircularProgress10, TextField as TextField5, Typography as Typography21 } from "@mui/material";
6951
+ import { useNavigate as useNavigate7 } from "react-router-dom";
6952
+ import { useDispatch as useDispatch5 } from "react-redux";
6953
+
6954
+ // src/utils/cambiarClave.ts
6955
+ var MENSAJE_POR_OMISION = "No se pudo cambiar la contrase\xF1a";
6956
+ function resultadoDeCambioDeClave(urlFinal, html) {
6957
+ if (/\/login\/?(\?.*)?$/.test(urlFinal)) return { ok: true };
6958
+ const mensajes = Array.from(new DOMParser().parseFromString(html, "text/html").querySelectorAll(".error-message")).map((elemento) => (elemento.textContent ?? "").trim()).filter((mensaje) => mensaje !== "");
6959
+ return { ok: false, mensaje: mensajes.join("\n") || MENSAJE_POR_OMISION };
6960
+ }
6961
+
6962
+ // src/pages/CambiarClavePage.tsx
6963
+ import { jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
6964
+ var CambiarClavePage = () => {
6965
+ const { clientContext, setIsLoggedIn, avisarCierreVoluntario } = useApp();
6966
+ const dispatch = useDispatch5();
6967
+ const navigate = useNavigate7();
6968
+ const [claveActual, setClaveActual] = useState24("");
6969
+ const [claveNueva, setClaveNueva] = useState24("");
6970
+ const [claveRepetida, setClaveRepetida] = useState24("");
6971
+ const [error, setError] = useState24(null);
6972
+ const [enviando, setEnviando] = useState24(false);
6973
+ const enviar = async (evento) => {
6974
+ evento.preventDefault();
6975
+ setError(null);
6976
+ if (claveNueva !== claveRepetida) {
6977
+ setError("La clave nueva y su repetici\xF3n no coinciden");
6978
+ return;
6979
+ }
6980
+ setEnviando(true);
6981
+ avisarCierreVoluntario(true);
6982
+ try {
6983
+ const respuesta = await fetchApi("/chpass", {
6984
+ method: "POST",
6985
+ body: new URLSearchParams({ oldPassword: claveActual, newPassword: claveNueva, repPassword: claveRepetida })
6986
+ });
6987
+ const resultado = resultadoDeCambioDeClave(respuesta.url, await respuesta.text());
6988
+ if (!resultado.ok) {
6989
+ avisarCierreVoluntario(false);
6990
+ setError(resultado.mensaje);
6991
+ return;
6992
+ }
6993
+ setIsLoggedIn(false);
6994
+ dispatch(clearClientContext());
6995
+ dispatch(setRedirectPath(null));
6996
+ navigate("/login", { replace: true, state: { claveCambiada: true } });
6997
+ } catch (err) {
6998
+ avisarCierreVoluntario(false);
6999
+ setError(mensajeDeError(err, "No se pudo cambiar la contrase\xF1a"));
7000
+ } finally {
7001
+ setEnviando(false);
7002
+ }
7003
+ };
7004
+ return /* @__PURE__ */ jsx40(Box27, { sx: { p: 3, display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxs30(Box27, { component: "form", onSubmit: enviar, sx: { width: "100%", maxWidth: 420 }, children: [
7005
+ /* @__PURE__ */ jsx40(Typography21, { variant: "h5", gutterBottom: true, children: "Cambiar contrase\xF1a" }),
7006
+ /* @__PURE__ */ jsx40(TextField5, { label: "Usuario", value: clientContext?.username ?? "", fullWidth: true, margin: "normal", disabled: true, autoComplete: "username" }),
7007
+ /* @__PURE__ */ jsx40(
7008
+ TextField5,
7009
+ {
7010
+ label: "Contrase\xF1a actual",
7011
+ type: "password",
7012
+ value: claveActual,
7013
+ onChange: (e) => setClaveActual(e.target.value),
7014
+ fullWidth: true,
7015
+ margin: "normal",
7016
+ autoFocus: true,
7017
+ required: true,
7018
+ autoComplete: "current-password",
7019
+ disabled: enviando
7020
+ }
7021
+ ),
7022
+ /* @__PURE__ */ jsx40(
7023
+ TextField5,
7024
+ {
7025
+ label: "Contrase\xF1a nueva",
7026
+ type: "password",
7027
+ value: claveNueva,
7028
+ onChange: (e) => setClaveNueva(e.target.value),
7029
+ fullWidth: true,
7030
+ margin: "normal",
7031
+ required: true,
7032
+ autoComplete: "new-password",
7033
+ disabled: enviando
7034
+ }
7035
+ ),
7036
+ /* @__PURE__ */ jsx40(
7037
+ TextField5,
7038
+ {
7039
+ label: "Repetir contrase\xF1a nueva",
7040
+ type: "password",
7041
+ value: claveRepetida,
7042
+ onChange: (e) => setClaveRepetida(e.target.value),
7043
+ fullWidth: true,
7044
+ margin: "normal",
7045
+ required: true,
7046
+ autoComplete: "new-password",
7047
+ disabled: enviando
7048
+ }
7049
+ ),
7050
+ error && /* @__PURE__ */ jsx40(Alert9, { severity: "error", sx: { mt: 1, whiteSpace: "pre-line" }, children: error }),
7051
+ /* @__PURE__ */ jsxs30(Box27, { sx: { display: "flex", gap: 1, justifyContent: "flex-end", mt: 2 }, children: [
7052
+ /* @__PURE__ */ jsx40(Button12, { onClick: () => navigate(-1), disabled: enviando, children: "Cancelar" }),
7053
+ /* @__PURE__ */ jsx40(Button12, { type: "submit", variant: "contained", disabled: enviando, children: enviando ? /* @__PURE__ */ jsx40(CircularProgress10, { size: 20 }) : "Cambiar contrase\xF1a" })
7054
+ ] }),
7055
+ /* @__PURE__ */ jsx40(Typography21, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 2 }, children: "Al cambiarla se cierra la sesi\xF3n: hay que ingresar de nuevo con la clave nueva." })
7056
+ ] }) });
7057
+ };
7058
+ var CambiarClavePage_default = CambiarClavePage;
7059
+
6632
7060
  // src/App.tsx
6633
7061
  import typeStore6 from "type-store";
6634
- import { Fragment as Fragment7, jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
7062
+ import { Fragment as Fragment8, jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
6635
7063
  var LocationTracker = () => {
6636
- const location = useLocation8();
7064
+ const location = useLocation9();
6637
7065
  const dispatch = useAppDispatch();
6638
7066
  useEffect18(() => {
6639
7067
  dispatch(setCurrentPath(location.pathname + location.search));
@@ -6641,45 +7069,46 @@ var LocationTracker = () => {
6641
7069
  return null;
6642
7070
  };
6643
7071
  function FrontendPlusReactRoutes({ myRoutes, myUnloggedRoutes, layout: Layout = MainLayout_default, unloggedLayout: UnloggedLayout }) {
6644
- return /* @__PURE__ */ jsxs29(Fragment7, { children: [
6645
- /* @__PURE__ */ jsx39(InitialRedirectHandler_default, {}),
6646
- puenteBPHabilitado() && /* @__PURE__ */ jsx39(CapturaBP, {}),
6647
- /* @__PURE__ */ jsxs29(Routes, { children: [
6648
- /* @__PURE__ */ jsxs29(Route, { element: UnloggedLayout ? /* @__PURE__ */ jsx39(UnloggedLayout, {}) : /* @__PURE__ */ jsx39(Outlet2, {}), children: [
6649
- /* @__PURE__ */ jsx39(Route, { path: "/login", element: /* @__PURE__ */ jsx39(LoginPage_default, {}) }),
6650
- /* @__PURE__ */ jsx39(Route, { path: "/logout", element: /* @__PURE__ */ jsx39(LogoutPage_default, {}) }),
7072
+ return /* @__PURE__ */ jsxs31(Fragment8, { children: [
7073
+ /* @__PURE__ */ jsx41(InitialRedirectHandler_default, {}),
7074
+ puenteBPHabilitado() && /* @__PURE__ */ jsx41(CapturaBP, {}),
7075
+ /* @__PURE__ */ jsxs31(Routes, { children: [
7076
+ /* @__PURE__ */ jsxs31(Route, { element: UnloggedLayout ? /* @__PURE__ */ jsx41(UnloggedLayout, {}) : /* @__PURE__ */ jsx41(Outlet2, {}), children: [
7077
+ /* @__PURE__ */ jsx41(Route, { path: "/login", element: /* @__PURE__ */ jsx41(LoginPage_default, {}) }),
7078
+ /* @__PURE__ */ jsx41(Route, { path: "/logout", element: /* @__PURE__ */ jsx41(LogoutPage_default, {}) }),
6651
7079
  myUnloggedRoutes
6652
7080
  ] }),
6653
- /* @__PURE__ */ jsxs29(Route, { element: /* @__PURE__ */ jsx39(PrivateRoute_default, { children: /* @__PURE__ */ jsx39(Layout, {}) }), children: [
6654
- /* @__PURE__ */ jsx39(Route, { path: "/", element: /* @__PURE__ */ jsx39(PantallaInicial_default, {}) }),
6655
- /* @__PURE__ */ jsx39(Route, { path: "/table/:tableName", element: /* @__PURE__ */ jsx39(GenericDataGridPage_default, {}) }),
6656
- /* @__PURE__ */ jsx39(Route, { path: "/menu/:menuName", element: /* @__PURE__ */ jsx39(MenuTablePage_default, {}) }),
6657
- /* @__PURE__ */ jsx39(Route, { path: "/procedures/:procedureName", element: /* @__PURE__ */ jsx39(ProcedureForm_default, {}) }),
6658
- Object.keys(wScreens).map((screenName) => /* @__PURE__ */ jsx39(
7081
+ /* @__PURE__ */ jsxs31(Route, { element: /* @__PURE__ */ jsx41(PrivateRoute_default, { children: /* @__PURE__ */ jsx41(Layout, {}) }), children: [
7082
+ /* @__PURE__ */ jsx41(Route, { path: "/", element: /* @__PURE__ */ jsx41(PantallaInicial_default, {}) }),
7083
+ /* @__PURE__ */ jsx41(Route, { path: "/table/:tableName", element: /* @__PURE__ */ jsx41(GenericDataGridPage_default, {}) }),
7084
+ /* @__PURE__ */ jsx41(Route, { path: "/menu/*", element: /* @__PURE__ */ jsx41(MenuTablePage_default, {}) }),
7085
+ /* @__PURE__ */ jsx41(Route, { path: "/procedures/:procedureName", element: /* @__PURE__ */ jsx41(ProcedureForm_default, {}) }),
7086
+ /* @__PURE__ */ jsx41(Route, { path: "/chpass", element: /* @__PURE__ */ jsx41(CambiarClavePage_default, {}) }),
7087
+ Object.keys(wScreens).map((screenName) => /* @__PURE__ */ jsx41(
6659
7088
  Route,
6660
7089
  {
6661
7090
  path: `/wScreens/${screenName}`,
6662
- element: /* @__PURE__ */ jsx39(PantallaDeRuta, { screenName })
7091
+ element: /* @__PURE__ */ jsx41(PantallaDeRuta, { screenName })
6663
7092
  },
6664
7093
  screenName
6665
7094
  )),
6666
- /* @__PURE__ */ jsx39(Route, { path: "/wScreens-fallback/:screenName", element: /* @__PURE__ */ jsx39(PantallaDeRespaldo, {}) }),
6667
- puenteBPHabilitado() && /* @__PURE__ */ jsx39(Route, { path: "/bp/menu", element: /* @__PURE__ */ jsx39(PaginaBP, {}) }),
6668
- puenteBPHabilitado() && /* @__PURE__ */ jsx39(Route, { path: "/bp/table/:tableName", element: /* @__PURE__ */ jsx39(PaginaGrillaBP, {}) }),
7095
+ /* @__PURE__ */ jsx41(Route, { path: "/wScreens-fallback/:screenName", element: /* @__PURE__ */ jsx41(PantallaDeRespaldo, {}) }),
7096
+ puenteBPHabilitado() && /* @__PURE__ */ jsx41(Route, { path: "/bp/menu", element: /* @__PURE__ */ jsx41(PaginaBP, {}) }),
7097
+ puenteBPHabilitado() && /* @__PURE__ */ jsx41(Route, { path: "/bp/table/:tableName", element: /* @__PURE__ */ jsx41(PaginaGrillaBP, {}) }),
6669
7098
  myRoutes,
6670
- /* @__PURE__ */ jsx39(Route, { path: "*", element: /* @__PURE__ */ jsx39("div", { style: { marginTop: "20px", marginLeft: "10px" }, children: "404 - Recurso No Encontrado" }) })
7099
+ /* @__PURE__ */ jsx41(Route, { path: "*", element: /* @__PURE__ */ jsx41("div", { style: { marginTop: "20px", marginLeft: "10px" }, children: "404 - Recurso No Encontrado" }) })
6671
7100
  ] }),
6672
- /* @__PURE__ */ jsx39(Route, { path: "*", element: /* @__PURE__ */ jsx39(Navigate4, { to: "/", replace: true }) })
7101
+ /* @__PURE__ */ jsx41(Route, { path: "*", element: /* @__PURE__ */ jsx41(Navigate4, { to: "/", replace: true }) })
6673
7102
  ] })
6674
7103
  ] });
6675
7104
  }
6676
7105
  var FrontendPlusProviders = ({ children, publicPaths }) => {
6677
- return /* @__PURE__ */ jsx39(Provider, { store, children: /* @__PURE__ */ jsx39(PersistGate, { loading: null, persistor, children: /* @__PURE__ */ jsxs29(BrowserRouter, { basename: rutaBase, children: [
6678
- /* @__PURE__ */ jsx39(LocationTracker, {}),
7106
+ return /* @__PURE__ */ jsx41(Provider, { store, children: /* @__PURE__ */ jsx41(PersistGate, { loading: null, persistor, children: /* @__PURE__ */ jsxs31(BrowserRouter, { basename: rutaBase, children: [
7107
+ /* @__PURE__ */ jsx41(LocationTracker, {}),
6679
7108
  " ",
6680
- /* @__PURE__ */ jsx39(AppProvider, { publicPaths, children: /* @__PURE__ */ jsxs29(SnackbarProvider, { children: [
7109
+ /* @__PURE__ */ jsx41(AppProvider, { publicPaths, children: /* @__PURE__ */ jsxs31(SnackbarProvider, { children: [
6681
7110
  children,
6682
- /* @__PURE__ */ jsx39(SessionExpiredMessage_default, {})
7111
+ /* @__PURE__ */ jsx41(SessionExpiredMessage_default, {})
6683
7112
  ] }) })
6684
7113
  ] }) }) });
6685
7114
  };
@@ -6691,9 +7120,10 @@ var App = ({
6691
7120
  myClientSides,
6692
7121
  myResultsOk,
6693
7122
  myConditions,
6694
- puenteBP
7123
+ puenteBP,
7124
+ recuperarClave
6695
7125
  }) => {
6696
- useMemo16(() => {
7126
+ useMemo17(() => {
6697
7127
  if (myClientSides) {
6698
7128
  extendClientSides(myClientSides);
6699
7129
  }
@@ -6709,7 +7139,8 @@ var App = ({
6709
7139
  if (puenteBP) {
6710
7140
  habilitarPuenteBP();
6711
7141
  }
6712
- }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP]);
7142
+ configurarRecuperarClave(recuperarClave);
7143
+ }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP, recuperarClave]);
6713
7144
  useEffect18(() => {
6714
7145
  typeStore6.messages = typeStore6.i18n.messages.es;
6715
7146
  typeStore6.locale = typeStore6.i18n.locale.es;
@@ -6719,11 +7150,11 @@ var App = ({
6719
7150
  link.href = `${envConfig.backendUrl}/img/logo-128.png`;
6720
7151
  document.getElementsByTagName("head")[0].appendChild(link);
6721
7152
  }, []);
6722
- const safePaths = React33.useMemo(() => {
7153
+ const safePaths = React34.useMemo(() => {
6723
7154
  const extracted = extractPathsFromRoutes(myUnloggedRoutes);
6724
7155
  return ["/login", "/logout", ...extracted];
6725
7156
  }, [myUnloggedRoutes]);
6726
- return /* @__PURE__ */ jsx39(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ jsx39(
7157
+ return /* @__PURE__ */ jsx41(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ jsx41(
6727
7158
  FrontendPlusReactRoutes,
6728
7159
  {
6729
7160
  myRoutes,