frontend-plus-react 0.7.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
@@ -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;
@@ -2005,9 +2021,10 @@ var defaultColumnHeaderCellRenderer = (props, fieldDef, ocultarColumna) => {
2005
2021
  {
2006
2022
  variant: "body2",
2007
2023
  sx: {
2008
- fontWeight: "bold",
2024
+ fontWeight: 500,
2025
+ color: "#37474f",
2009
2026
  textDecoration: isPrimaryKey ? "underline" : "none",
2010
- fontSize: "0.930rem",
2027
+ fontSize: "0.85rem",
2011
2028
  textAlign: "left",
2012
2029
  flexGrow: 1,
2013
2030
  overflow: "hidden",
@@ -2033,22 +2050,20 @@ var ActionsColumnHeaderCell = ({
2033
2050
  allowInsert && /* @__PURE__ */ jsx6(
2034
2051
  IconButton2,
2035
2052
  {
2036
- color: "inherit",
2037
2053
  onClick: () => handleAddRow(),
2038
2054
  size: "small",
2039
2055
  title: "A\xF1adir registro",
2040
- sx: { p: 0.25 },
2041
- 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 } })
2042
2058
  }
2043
2059
  ),
2044
2060
  /* @__PURE__ */ jsx6(
2045
2061
  IconButton2,
2046
2062
  {
2047
- color: "inherit",
2048
2063
  onClick: toggleFilterVisibility,
2049
2064
  size: "small",
2050
2065
  title: isFilterRowVisible ? "Ocultar filtros" : "Mostrar filtros",
2051
- sx: { p: 0.25 },
2066
+ sx: { p: 0.25, color: "grey.800" },
2052
2067
  children: isFilterRowVisible ? /* @__PURE__ */ jsx6(SearchOffIcon, { sx: { fontSize: 18 } }) : /* @__PURE__ */ jsx6(SearchIcon, { sx: { fontSize: 18 } })
2053
2068
  }
2054
2069
  ),
@@ -2058,7 +2073,7 @@ var ActionsColumnHeaderCell = ({
2058
2073
  onClick: (event) => handleDataGridOptions(event),
2059
2074
  size: "small",
2060
2075
  title: "Opciones de esta tabla",
2061
- sx: { p: 0.25 },
2076
+ sx: { p: 0.25, color: "grey.800" },
2062
2077
  children: /* @__PURE__ */ jsx6(MoreVertIcon, { sx: { fontSize: 20, variant: "filled", size: "small" } })
2063
2078
  }
2064
2079
  )
@@ -2548,9 +2563,12 @@ var TypedField = ({ fieldDef, value, onChange, onCommit, disabled, isFilterMode
2548
2563
  "& .MuiOutlinedInput-root": {
2549
2564
  backgroundColor: "background.paper",
2550
2565
  height: FILTER_ROW_HEIGHT - 5,
2551
- minWidth: 50,
2566
+ minWidth: 30,
2552
2567
  fontSize: "0.8rem",
2553
2568
  padding: "0px"
2569
+ },
2570
+ "& .MuiOutlinedInput-input": {
2571
+ padding: "0 4px"
2554
2572
  }
2555
2573
  } : {
2556
2574
  "& .MuiOutlinedInput-root": {
@@ -2597,36 +2615,42 @@ function FilterInputRenderer({
2597
2615
  [columnKey]: { operator: newOp, value: formattedVal }
2598
2616
  }));
2599
2617
  };
2600
- return /* @__PURE__ */ jsxs10(Box4, { sx: { mt: 0, display: "flex", alignItems: "center", gap: 0.5, pr: 1 }, children: [
2601
- /* @__PURE__ */ jsx12(
2602
- "select",
2603
- {
2604
- value: operator,
2605
- onChange: (e) => handleFilterChange(e.target.value, val),
2606
- style: {
2607
- height: FILTER_ROW_HEIGHT - 5,
2608
- minWidth: 40,
2609
- backgroundColor: "white",
2610
- border: "1px solid #ccc",
2611
- borderRadius: "4px",
2612
- fontSize: "0.8rem",
2613
- padding: "0 2px",
2614
- textAlign: "center",
2615
- cursor: "pointer"
2616
- },
2617
- children: filterOperators.map((op) => /* @__PURE__ */ jsx12("option", { value: op.value, children: op.label }, op.value))
2618
- }
2619
- ),
2620
- /* @__PURE__ */ jsx12(
2621
- TypedField,
2622
- {
2623
- fieldDef,
2624
- isFilterMode: true,
2625
- value: val,
2626
- onChange: (_name, newValue, _isValid) => handleFilterChange(operator, newValue)
2627
- }
2628
- )
2629
- ] });
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
+ );
2630
2654
  }
2631
2655
  return /* @__PURE__ */ jsx12(
2632
2656
  InputBase3,
@@ -2662,7 +2686,7 @@ var defaultColumnSummaryCellRenderer = (props, fixedFields) => {
2662
2686
  const { filters, setFilters } = row;
2663
2687
  if (valorFijoDe(fixedFields, column.key)) return null;
2664
2688
  const fieldDef = esColumnaPropia(column) && column.customType === "default" ? column.fieldDef : void 0;
2665
- 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(
2666
2690
  FilterInputRender_default,
2667
2691
  {
2668
2692
  columnKey: column.key,
@@ -2723,7 +2747,7 @@ var detailColumnCellSummaryRenderer = (_props) => null;
2723
2747
 
2724
2748
  // src/components/grid/renderers/cellRenderers.tsx
2725
2749
  import React11 from "react";
2726
- 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";
2727
2751
  import ViewHeadlineIcon from "@mui/icons-material/ViewHeadline";
2728
2752
  import DeleteIcon from "@mui/icons-material/Delete";
2729
2753
  import AddIcon2 from "@mui/icons-material/Add";
@@ -2889,14 +2913,12 @@ var allColumnsCellRenderer = (props) => {
2889
2913
  }
2890
2914
  ];
2891
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(
2892
- Button4,
2916
+ IconButton5,
2893
2917
  {
2894
- variant: "outlined",
2895
- color: actionDef.color,
2896
2918
  size: "small",
2897
2919
  onClick: () => actionDef.handler(row),
2898
2920
  title: actionDef.title,
2899
- 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` } },
2900
2922
  children: /* @__PURE__ */ jsx16(actionDef.icon, { sx: { fontSize: 18 } })
2901
2923
  },
2902
2924
  actionDef.action
@@ -3306,20 +3328,36 @@ function campoDibujable(field) {
3306
3328
 
3307
3329
  // src/hooks/grid/useGridColumns.ts
3308
3330
  var estimateTextWidth = (text) => text.length * 8 + 16;
3309
- 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) => {
3310
3346
  const anchos = /* @__PURE__ */ new Map();
3347
+ const anchosConTituloCompleto = /* @__PURE__ */ new Map();
3311
3348
  const muestra = filas.slice(0, 50);
3312
3349
  tableDefinition.fields.forEach((fieldDef) => {
3313
3350
  if (typeof fieldDef.width === "number" && fieldDef.width > 0) {
3314
3351
  anchos.set(fieldDef.name, fieldDef.width);
3352
+ anchosConTituloCompleto.set(fieldDef.name, fieldDef.width);
3315
3353
  return;
3316
3354
  }
3317
3355
  const headerLabel = fieldDef.label || fieldDef.name;
3318
3356
  const type = fieldDef.typeName?.toLowerCase() || "";
3319
- let typeMinWidth = 60;
3320
- if (type.includes("boolean") || type.includes("checkbox")) typeMinWidth = 60;
3321
- else if (type.includes("date") || type.includes("timestamp")) typeMinWidth = 110;
3322
- 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;
3323
3361
  const headerWidth = estimateTextWidth(headerLabel) + 20;
3324
3362
  const typer = typerFrom(fieldDef);
3325
3363
  const maxDataWidth = muestra.reduce((maxW, row) => {
@@ -3333,9 +3371,12 @@ var calcularAnchosDeColumnas = (tableDefinition, filas) => {
3333
3371
  }
3334
3372
  return Math.max(maxW, estimateTextWidth(texto));
3335
3373
  }, 0);
3336
- 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)));
3337
3377
  });
3338
- return anchos;
3378
+ const sumaCompleta = [...anchos.keys()].reduce((total, nombre) => total + (anchosConTituloCompleto.get(nombre) ?? 0), 0);
3379
+ return sumaCompleta <= anchoDisponible ? anchosConTituloCompleto : anchos;
3339
3380
  };
3340
3381
  var useGridColumns = ({
3341
3382
  tableDefinition,
@@ -3455,7 +3496,7 @@ import {
3455
3496
  DialogTitle as DialogTitle2,
3456
3497
  DialogContent as DialogContent2,
3457
3498
  DialogActions as DialogActions2,
3458
- Button as Button5,
3499
+ Button as Button4,
3459
3500
  FormControlLabel,
3460
3501
  Checkbox,
3461
3502
  Box as Box9,
@@ -3579,9 +3620,9 @@ var ImportDialog = ({ open, onClose, onImport, loading }) => {
3579
3620
  ] })
3580
3621
  ] }) }),
3581
3622
  /* @__PURE__ */ jsxs14(DialogActions2, { sx: { px: 3, py: 2 }, children: [
3582
- /* @__PURE__ */ jsx19(Button5, { onClick: onClose, disabled: loading, children: "Cancelar" }),
3623
+ /* @__PURE__ */ jsx19(Button4, { onClick: onClose, disabled: loading, children: "Cancelar" }),
3583
3624
  /* @__PURE__ */ jsx19(
3584
- Button5,
3625
+ Button4,
3585
3626
  {
3586
3627
  onClick: handleConfirm,
3587
3628
  variant: "contained",
@@ -3601,7 +3642,7 @@ import {
3601
3642
  DialogTitle as DialogTitle3,
3602
3643
  DialogContent as DialogContent3,
3603
3644
  DialogActions as DialogActions3,
3604
- Button as Button6,
3645
+ Button as Button5,
3605
3646
  FormControl as FormControl2,
3606
3647
  FormLabel,
3607
3648
  RadioGroup,
@@ -3809,14 +3850,14 @@ var ExportDialog = ({
3809
3850
  ] })
3810
3851
  ] }) }),
3811
3852
  /* @__PURE__ */ jsxs15(DialogActions3, { children: [
3812
- /* @__PURE__ */ jsx20(Button6, { onClick: onClose, disabled: isPreparing, children: "Cancelar" }),
3813
- /* @__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" })
3814
3855
  ] })
3815
3856
  ] });
3816
3857
  };
3817
3858
 
3818
3859
  // src/components/grid/DialogoDeColumnas.tsx
3819
- import { Box as Box11, Button as Button7, 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";
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";
3820
3861
  import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3821
3862
  var DialogoDeColumnas = ({ open, onClose, columnas, ocultas, alternarColumna }) => {
3822
3863
  const lista = (titulo, nombres, idDeLista) => /* @__PURE__ */ jsxs16(Box11, { sx: { flex: 1, minWidth: 0 }, children: [
@@ -3829,7 +3870,7 @@ var DialogoDeColumnas = ({ open, onClose, columnas, ocultas, alternarColumna })
3829
3870
  lista("Columnas mostradas", columnas.filter((nombre) => !ocultas.has(nombre)), "columnas-mostradas"),
3830
3871
  lista("Columnas ocultas", columnas.filter((nombre) => ocultas.has(nombre)), "columnas-ocultas")
3831
3872
  ] }) }),
3832
- /* @__PURE__ */ jsx21(DialogActions4, { children: /* @__PURE__ */ jsx21(Button7, { onClick: onClose, children: "Cerrar" }) })
3873
+ /* @__PURE__ */ jsx21(DialogActions4, { children: /* @__PURE__ */ jsx21(Button6, { onClick: onClose, children: "Cerrar" }) })
3833
3874
  ] });
3834
3875
  };
3835
3876
 
@@ -3839,11 +3880,11 @@ import {
3839
3880
  DialogTitle as DialogTitle5,
3840
3881
  DialogContent as DialogContent5,
3841
3882
  DialogActions as DialogActions5,
3842
- Button as Button8,
3883
+ Button as Button7,
3843
3884
  Alert,
3844
3885
  Box as Box12,
3845
3886
  CircularProgress as CircularProgress3,
3846
- IconButton as IconButton5,
3887
+ IconButton as IconButton6,
3847
3888
  Typography as Typography10
3848
3889
  } from "@mui/material";
3849
3890
  import CloseIcon from "@mui/icons-material/Close";
@@ -3983,7 +4024,7 @@ var VerticalEditorPage = ({
3983
4024
  " - ",
3984
4025
  tableDefinition.title || tableName
3985
4026
  ] }),
3986
- /* @__PURE__ */ jsx22(IconButton5, { onClick: onClose, size: "small", edge: "end", children: /* @__PURE__ */ jsx22(CloseIcon, {}) })
4027
+ /* @__PURE__ */ jsx22(IconButton6, { onClick: onClose, size: "small", edge: "end", children: /* @__PURE__ */ jsx22(CloseIcon, {}) })
3987
4028
  ] }),
3988
4029
  /* @__PURE__ */ jsxs17(DialogContent5, { dividers: true, sx: { px: 2, flexGrow: 1, py: 1 }, children: [
3989
4030
  globalError && /* @__PURE__ */ jsx22(Alert, { severity: "error", sx: { mb: 2 }, children: globalError }),
@@ -4009,7 +4050,7 @@ var VerticalEditorPage = ({
4009
4050
  isSaving && /* @__PURE__ */ jsx22(CircularProgress3, { size: 20, sx: { ml: 2 } }),
4010
4051
  /* @__PURE__ */ jsx22("span", { style: { marginLeft: 10, fontSize: "0.8rem", color: "#666" }, children: isSaving ? "Guardando..." : "Cambios auto-guardados" })
4011
4052
  ] }),
4012
- /* @__PURE__ */ jsx22(Button8, { variant: "outlined", onClick: onClose, children: "Cerrar" })
4053
+ /* @__PURE__ */ jsx22(Button7, { variant: "outlined", onClick: onClose, children: "Cerrar" })
4013
4054
  ] })
4014
4055
  ] });
4015
4056
  };
@@ -4097,6 +4138,7 @@ var ALTO_TITULO_ANIDADA = 26;
4097
4138
  var ALTO_MINIMO_CUERPO_ANIDADA = 120;
4098
4139
  var HOLGURA_ANIDADA = 24;
4099
4140
  var ANCHO_MINIMO_ANIDADA = 240;
4141
+ var ANCHO_RESERVADO_PANTALLA = 320;
4100
4142
  var SANGRIA_DETALLE = 28 + 8 + 3;
4101
4143
  var MARGEN_INFERIOR_PAGINA = 24;
4102
4144
  var PRIMERAS_FILAS_ANIDADA = 20;
@@ -4312,7 +4354,7 @@ var GenericDataGrid = ({
4312
4354
  const firstDisplayCount = tableDefinition?.firstDisplayCount ?? PRIMERAS_FILAS_ANIDADA;
4313
4355
  const firstDisplayOverLimit = tableDefinition?.firstDisplayOverLimit ?? LIMITE_PRIMERAS_FILAS_ANIDADA;
4314
4356
  const filasVisiblesMax = anidada ? filteredRows.length <= firstDisplayOverLimit ? filteredRows.length : firstDisplayCount : void 0;
4315
- const conPie = !anidada || !!tableDefinition?.fields.some((field) => field.aggregate);
4357
+ const conPie = !!tableDefinition?.fields.some((field) => field.aggregate);
4316
4358
  const { gridHeight, bottomSummaryRow, summaryRows, filterHeight } = useGridLayout({
4317
4359
  filteredRows,
4318
4360
  isFilterRowVisible,
@@ -4406,7 +4448,7 @@ var GenericDataGrid = ({
4406
4448
  const definition = parametros.tableDef ? fusionar(definicionDelServidor, parametros.tableDef) : definicionDelServidor;
4407
4449
  setTableDefinition(definition);
4408
4450
  setSortColumns(getInitialSortColumns(definition));
4409
- setAnchosCalculados(calcularAnchosDeColumnas(definition, data));
4451
+ setAnchosCalculados(calcularAnchosDeColumnas(definition, data, window.innerWidth - ANCHO_RESERVADO_PANTALLA));
4410
4452
  setColumnasOcultas(columnasOcultasIniciales(definition));
4411
4453
  setTableData(data);
4412
4454
  const filasDeFiltros = parametros.filterColumns ?? aFiltrosIniciales(definition.filterColumns);
@@ -4607,7 +4649,7 @@ var GenericDataGrid = ({
4607
4649
  [exitingRowIds, primaryKey]
4608
4650
  );
4609
4651
  const anchoContenido = useMemo10(() => {
4610
- if (!anidada || columns.length === 0) return void 0;
4652
+ if (columns.length === 0) return void 0;
4611
4653
  const sumaColumnas = columns.reduce((total, col) => {
4612
4654
  const medido = columnWidths.get(col.key)?.width;
4613
4655
  return total + (typeof medido === "number" ? medido : typeof col.width === "number" ? col.width : 100);
@@ -4617,8 +4659,9 @@ var GenericDataGrid = ({
4617
4659
  return Math.max(ANCHO_MINIMO_ANIDADA, propio, anchoDetalleMax > 0 ? anchoDetalleMax + SANGRIA_DETALLE + (medidas?.barra ?? 0) + 2 : 0);
4618
4660
  }, [anidada, columns, columnWidths, medidas?.barra, detallesAbiertos, anchosDetalle]);
4619
4661
  useEffect6(() => {
4620
- if (anchoContenido !== void 0) onAnchoDeseado?.(anchoContenido);
4621
- }, [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;
4622
4665
  const estiloGrilla = useMemo10(() => ({ height: "100%", width: "100%", boxSizing: "border-box", ...gridStyles }), [gridStyles]);
4623
4666
  const bottomSummaryRows = useMemo10(() => conPie ? [bottomSummaryRow] : [], [conPie, bottomSummaryRow]);
4624
4667
  const vistaGrilla = useMemo10(() => ({ isFilterRowVisible }), [isFilterRowVisible]);
@@ -4644,7 +4687,7 @@ var GenericDataGrid = ({
4644
4687
  if (!tableDefinition) {
4645
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." }) });
4646
4689
  }
4647
- 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: anchoContenido ?? void 0, maxWidth: "100%" }, children: [
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: [
4648
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(
4649
4692
  StaticAncestorGrid,
4650
4693
  {
@@ -4741,7 +4784,7 @@ var GenericDataGrid = ({
4741
4784
  sx: {
4742
4785
  position: "absolute",
4743
4786
  top: filterHeight + HEADER_ROW_HEIGHT + 1,
4744
- bottom: anidada ? (conPie ? SUMMARY_ROW_HEIGHT : 0) + 4 : 102,
4787
+ bottom: anidada ? (conPie ? SUMMARY_ROW_HEIGHT : 0) + 4 : conPie ? 102 : 102 - SUMMARY_ROW_HEIGHT,
4745
4788
  left: 18,
4746
4789
  right: 0,
4747
4790
  display: "flex",
@@ -4866,8 +4909,8 @@ var GenericDataGrid = ({
4866
4909
  var GenericDataGrid_default = GenericDataGrid;
4867
4910
 
4868
4911
  // src/App.tsx
4869
- import React33, { useEffect as useEffect18, useMemo as useMemo16 } from "react";
4870
- 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";
4871
4914
 
4872
4915
  // src/components/MainLayout.tsx
4873
4916
  import { useEffect as useEffect9 } from "react";
@@ -4877,10 +4920,10 @@ import {
4877
4920
  AppBar,
4878
4921
  Toolbar,
4879
4922
  Typography as Typography13,
4880
- Button as Button9,
4923
+ Button as Button8,
4881
4924
  Drawer,
4882
4925
  CssBaseline,
4883
- IconButton as IconButton6,
4926
+ IconButton as IconButton8,
4884
4927
  useTheme as useTheme5,
4885
4928
  useMediaQuery
4886
4929
  } from "@mui/material";
@@ -4889,6 +4932,15 @@ import {
4889
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";
4890
4933
  import { useLocation, useNavigate } from "react-router-dom";
4891
4934
  import { useSelector as useSelector2, useDispatch as useDispatch2 } from "react-redux";
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
4892
4944
  import { jsx as jsx24 } from "react/jsx-runtime";
4893
4945
  var AuthContext = createContext4(void 0);
4894
4946
  var useApp = () => {
@@ -4905,9 +4957,10 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4905
4957
  const [showSessionExpiredMessage, setShowSessionExpiredMessage] = useState17(false);
4906
4958
  const isRedirectingRef = useRef8(false);
4907
4959
  const huboSesionRef = useRef8(false);
4960
+ const cierreVoluntarioRef = useRef8(false);
4908
4961
  const dispatch = useDispatch2();
4909
4962
  const clientContext = useSelector2((state) => state.clientContext);
4910
- if (clientContext.status === "succeeded") huboSesionRef.current = true;
4963
+ if (clientContext.status === "succeeded" && !cierreVoluntarioRef.current) huboSesionRef.current = true;
4911
4964
  const navigate = useNavigate();
4912
4965
  const handleExpiredSessionRedirect = useCallback13(() => {
4913
4966
  isRedirectingRef.current = true;
@@ -4918,6 +4971,17 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4918
4971
  isRedirectingRef.current = false;
4919
4972
  }, 2e3);
4920
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]);
4921
4985
  const location = useLocation();
4922
4986
  const isCurrentPathPublic = useCallback13(() => {
4923
4987
  const current = location.pathname;
@@ -4943,8 +5007,8 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4943
5007
  }
4944
5008
  } else {
4945
5009
  setIsLoggedIn(false);
4946
- dispatch(clearClientContext());
4947
- if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current) {
5010
+ limpiarContextoPorSesionPerdida();
5011
+ if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current && !cierreVoluntarioRef.current) {
4948
5012
  console.warn("Sesi\xF3n inv\xE1lida en ruta protegida. Mostrando modal.");
4949
5013
  setShowSessionExpiredMessage(true);
4950
5014
  } else {
@@ -4954,13 +5018,13 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4954
5018
  } catch (error) {
4955
5019
  console.error("Error checking session:", error);
4956
5020
  setIsLoggedIn(false);
4957
- dispatch(clearClientContext());
5021
+ limpiarContextoPorSesionPerdida();
4958
5022
  const isPublic = publicPaths.some((p) => location.pathname === p || location.pathname.startsWith(p + "/"));
4959
- if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current) {
5023
+ if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current && !cierreVoluntarioRef.current) {
4960
5024
  setShowSessionExpiredMessage(true);
4961
5025
  }
4962
5026
  }
4963
- }, [dispatch, clientContext.status, showSessionExpiredMessage, isCurrentPathPublic]);
5027
+ }, [dispatch, clientContext.status, showSessionExpiredMessage, isCurrentPathPublic, limpiarContextoPorSesionPerdida]);
4964
5028
  useEffect7(() => {
4965
5029
  checkSession();
4966
5030
  const intervalId = setInterval(checkSession, 15 * 1e3);
@@ -4974,26 +5038,30 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4974
5038
  checkSession,
4975
5039
  showSessionExpiredMessage,
4976
5040
  setShowSessionExpiredMessage,
4977
- handleExpiredSessionRedirect
5041
+ handleExpiredSessionRedirect,
5042
+ avisarCierreVoluntario
4978
5043
  }), [
4979
5044
  isLoggedIn,
4980
5045
  setIsLoggedIn,
4981
5046
  checkSession,
4982
5047
  showSessionExpiredMessage,
4983
5048
  setShowSessionExpiredMessage,
4984
- handleExpiredSessionRedirect
5049
+ handleExpiredSessionRedirect,
5050
+ avisarCierreVoluntario
4985
5051
  ]);
4986
5052
  return /* @__PURE__ */ jsx24(AuthContext.Provider, { value: authContextValue, children });
4987
5053
  };
4988
5054
 
4989
5055
  // src/components/MainLayout.tsx
5056
+ import { useNavigate as useNavigate4 } from "react-router-dom";
4990
5057
  import LogoutIcon from "@mui/icons-material/Logout";
5058
+ import VpnKeyIcon from "@mui/icons-material/VpnKey";
4991
5059
  import MenuIcon from "@mui/icons-material/Menu";
4992
5060
  import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
4993
5061
 
4994
5062
  // src/components/SideMenu.tsx
4995
- import React19, { useEffect as useEffect8 } from "react";
4996
- import { List as List4, ListItem, ListItemButton as ListItemButton4, ListItemIcon as ListItemIcon2, ListItemText as ListItemText3, Divider as Divider2, Box as Box14, Typography as Typography12, 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";
4997
5065
  import ExpandLess from "@mui/icons-material/ExpandLess";
4998
5066
  import ExpandMore from "@mui/icons-material/ExpandMore";
4999
5067
  import TableChartIcon from "@mui/icons-material/TableChart";
@@ -5001,10 +5069,17 @@ import FolderIcon from "@mui/icons-material/Folder";
5001
5069
  import DnsIcon from "@mui/icons-material/Dns";
5002
5070
  import WarningAmberIcon3 from "@mui/icons-material/WarningAmber";
5003
5071
  import DesktopWindowsIcon from "@mui/icons-material/DesktopWindows";
5004
- import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
5005
- import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
5072
+ import SearchIcon3 from "@mui/icons-material/Search";
5073
+ import ClearIcon from "@mui/icons-material/Clear";
5006
5074
  import { useNavigate as useNavigate2, useLocation as useLocation2 } from "react-router-dom";
5007
- 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;
5008
5083
 
5009
5084
  // src/utils/menuInicial.ts
5010
5085
  function rutaDeTablaDeMenu(camino) {
@@ -5056,16 +5131,93 @@ function itemInicial(menu) {
5056
5131
  return elegido.menuType === "menu" ? itemInicial(elegido.menuContent) : elegido;
5057
5132
  }
5058
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
+
5059
5163
  // src/components/SideMenu.tsx
5060
- import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
5061
- var MenuListItem = ({ item, level, grupos = [], 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 }) => {
5062
5209
  const navigate = useNavigate2();
5063
5210
  const location = useLocation2();
5064
- const open = useSubMenuOpenState(item.name);
5211
+ const clave = claveDeMenu([...grupos, item.name]);
5212
+ const open = useSubMenuOpenState(clave);
5065
5213
  const dispatch = useAppDispatch();
5066
5214
  const handleClick = () => {
5067
5215
  if (item.menuType === "menu") {
5068
- dispatch(toggleSubMenu(item.name));
5216
+ if (open) {
5217
+ dispatch(setSubMenuOpen({ menuName: clave, isOpen: false }));
5218
+ } else {
5219
+ dispatch(abrirGrupo({ clave, hermanos }));
5220
+ }
5069
5221
  } else {
5070
5222
  navigate(rutaDeItemDeMenu(item, grupos));
5071
5223
  if (onMenuItemClick) {
@@ -5073,39 +5225,14 @@ var MenuListItem = ({ item, level, grupos = [], onMenuItemClick }) => {
5073
5225
  }
5074
5226
  }
5075
5227
  };
5076
- const getIcon = (item2) => {
5077
- switch (item2.menuType) {
5078
- case "table":
5079
- return /* @__PURE__ */ jsx25(TableChartIcon, { sx: { color: blue[700] } });
5080
- case "proc":
5081
- return /* @__PURE__ */ jsx25(DnsIcon, { sx: { color: teal[700] } });
5082
- case "menu":
5083
- return /* @__PURE__ */ jsx25(FolderIcon, { sx: { color: orange[800] } });
5084
- default:
5085
- const WScreenComponent = wScreens[item2.menuType];
5086
- if (WScreenComponent !== void 0) {
5087
- return /* @__PURE__ */ jsx25(DesktopWindowsIcon, { sx: { color: grey[700] } });
5088
- }
5089
- return /* @__PURE__ */ jsx25(WarningAmberIcon3, { sx: { color: red[500] } });
5090
- }
5091
- };
5092
- const currentPath = location.pathname;
5093
- let isSelected = false;
5094
- const pathParts = currentPath.split("/");
5095
- const urlTableName = pathParts[1] === "table" ? pathParts[2] : null;
5096
- if (item.menuType === "table") {
5097
- isSelected = currentPath === rutaDeTablaDeMenu([...grupos, item.name]) || urlTableName === (item.table || item.name);
5098
- } else if (item.menuType === "proc") {
5099
- isSelected = currentPath === `/procedures/${item.name}`;
5100
- } else if (item.menuType !== "menu") {
5101
- isSelected = currentPath.startsWith(`/wScreens/${item.menuType}`) || currentPath.startsWith(`/wScreens-fallback/${item.menuType}`);
5102
- }
5228
+ const isSelected = estaSeleccionado(item, grupos, location.pathname, location.hash);
5103
5229
  const itemRef = React19.useRef(null);
5104
5230
  useEffect8(() => {
5105
5231
  if (isSelected && itemRef.current) {
5106
5232
  itemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
5107
5233
  }
5108
5234
  }, [isSelected]);
5235
+ const contenido = item.menuType === "menu" ? item.menuContent : [];
5109
5236
  return /* @__PURE__ */ jsxs19("div", { ref: itemRef, children: [
5110
5237
  /* @__PURE__ */ jsxs19(
5111
5238
  ListItemButton4,
@@ -5114,83 +5241,156 @@ var MenuListItem = ({ item, level, grupos = [], onMenuItemClick }) => {
5114
5241
  sx: { pl: level * 2 },
5115
5242
  selected: isSelected,
5116
5243
  children: [
5117
- /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(item) }),
5118
- /* @__PURE__ */ jsx25(ListItemText3, { primary: item.label || item.name }),
5244
+ /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(item, isSelected || open) }),
5245
+ /* @__PURE__ */ jsx25(ListItemText3, { primary: etiquetaDeItem(item) }),
5119
5246
  item.menuType === "menu" ? open ? /* @__PURE__ */ jsx25(ExpandLess, {}) : /* @__PURE__ */ jsx25(ExpandMore, {}) : null
5120
5247
  ]
5121
5248
  }
5122
5249
  ),
5123
- item.menuType === "menu" && /* @__PURE__ */ jsx25(Collapse, { in: open, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx25(List4, { component: "div", disablePadding: true, children: item.menuContent.map((subItem) => /* @__PURE__ */ jsx25(
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(
5124
5251
  MenuListItem,
5125
5252
  {
5126
5253
  item: subItem,
5127
5254
  level: level + 1,
5128
5255
  grupos: [...grupos, item.name],
5256
+ hermanos: contenido.filter((otro) => otro !== subItem).map((otro) => claveDeMenu([...grupos, item.name, otro.name])),
5129
5257
  onMenuItemClick
5130
5258
  },
5131
5259
  subItem.name
5132
5260
  )) }) })
5133
5261
  ] });
5134
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
+ };
5135
5280
  var SideMenu = ({ onMenuItemClick }) => {
5136
5281
  const { clientContext } = useApp();
5137
5282
  const navigate = useNavigate2();
5138
5283
  const location = useLocation2();
5139
5284
  const dispatch = useAppDispatch();
5140
- const subMenuOpenStates = useAppSelector((state) => state.menuUi.subMenuOpenStates);
5141
- const allSubMenusKeys = Object.keys(subMenuOpenStates);
5142
- const openSubMenusCount = allSubMenusKeys.filter((key) => subMenuOpenStates[key]).length;
5143
- const isMostlyOpen = allSubMenusKeys.length > 0 && openSubMenusCount / allSubMenusKeys.length > 0.5;
5144
- const handleToggleAllSubMenus = () => {
5145
- dispatch(setAllSubMenusOpen(!isMostlyOpen));
5146
- };
5147
- if (!clientContext || !clientContext.menu) {
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]);
5292
+ useEffect8(() => {
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);
5301
+ }
5302
+ };
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) {
5148
5318
  return /* @__PURE__ */ jsx25(Box14, { sx: { p: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx25(Typography12, { variant: "body2", color: "text.secondary", children: "Cargando men\xFA..." }) });
5149
5319
  }
5150
- useEffect8(() => {
5151
- if (!clientContext?.menu) return;
5152
- const expandParents = (items, targetPath, parentNames = []) => {
5153
- const pathParts = targetPath.split("/");
5154
- const urlTableName = pathParts[1] === "table" ? pathParts[2] : null;
5155
- for (const item of items) {
5156
- let matches = false;
5157
- if (item.menuType === "table") {
5158
- matches = targetPath === rutaDeTablaDeMenu([...parentNames, item.name]) || urlTableName === (item.table || item.name);
5159
- } else if (item.menuType === "proc") {
5160
- matches = targetPath === `/procedures/${item.name}`;
5161
- } else if (item.menuType !== "menu") {
5162
- matches = targetPath.startsWith(`/wScreens/${item.menuType}`) || targetPath.startsWith(`/wScreens-fallback/${item.menuType}`);
5163
- }
5164
- if (matches) {
5165
- parentNames.forEach((name) => {
5166
- dispatch(setSubMenuOpen({ menuName: name, isOpen: true }));
5167
- });
5168
- return true;
5169
- }
5170
- if (item.menuType === "menu") {
5171
- const found = expandParents(item.menuContent, targetPath, [...parentNames, item.name]);
5172
- if (found) return true;
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,
5346
+ {
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
5173
5365
  }
5174
5366
  }
5175
- return false;
5176
- };
5177
- expandParents(clientContext.menu, location.pathname);
5178
- }, [location.pathname, clientContext, dispatch]);
5179
- return /* @__PURE__ */ jsxs19(Box14, { sx: { width: "100%", flexShrink: 0 }, children: [
5180
- clientContext.menu.some((item) => item.menuType === "menu") && /* @__PURE__ */ jsx25(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs19(ListItemButton4, { onClick: handleToggleAllSubMenus, children: [
5181
- /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: isMostlyOpen ? /* @__PURE__ */ jsx25(UnfoldLessIcon, {}) : /* @__PURE__ */ jsx25(UnfoldMoreIcon, {}) }),
5182
- /* @__PURE__ */ jsx25(ListItemText3, { primary: isMostlyOpen ? "colapsar " : "expandir" })
5183
- ] }) }),
5367
+ ) }),
5184
5368
  /* @__PURE__ */ jsx25(Divider2, {}),
5185
- /* @__PURE__ */ jsx25(List4, { children: clientContext.menu.map((menuItem) => /* @__PURE__ */ jsx25(
5186
- MenuListItem,
5187
- {
5188
- item: menuItem,
5189
- level: 1,
5190
- onMenuItemClick
5191
- },
5192
- menuItem.name
5193
- )) })
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
+ ] }) })
5194
5394
  ] });
5195
5395
  };
5196
5396
  var SideMenu_default = SideMenu;
@@ -5200,10 +5400,11 @@ import { useCallback as useCallback14 } from "react";
5200
5400
  import { useNavigate as useNavigate3 } from "react-router-dom";
5201
5401
  import { useDispatch as useDispatch3 } from "react-redux";
5202
5402
  var useLogout = () => {
5203
- const { setIsLoggedIn } = useApp();
5403
+ const { setIsLoggedIn, avisarCierreVoluntario } = useApp();
5204
5404
  const dispatch = useDispatch3();
5205
5405
  const navigate = useNavigate3();
5206
5406
  const logout = useCallback14(async () => {
5407
+ avisarCierreVoluntario(true);
5207
5408
  try {
5208
5409
  const response = await fetchApi("/logout", { method: "GET" });
5209
5410
  if (response.status === 401 || response.ok) {
@@ -5216,9 +5417,10 @@ var useLogout = () => {
5216
5417
  } finally {
5217
5418
  setIsLoggedIn(false);
5218
5419
  dispatch(clearClientContext());
5420
+ dispatch(setRedirectPath(null));
5219
5421
  navigate("/login", { replace: true });
5220
5422
  }
5221
- }, [setIsLoggedIn, dispatch, navigate]);
5423
+ }, [setIsLoggedIn, avisarCierreVoluntario, dispatch, navigate]);
5222
5424
  return logout;
5223
5425
  };
5224
5426
  var useLogout_default = useLogout;
@@ -5229,6 +5431,7 @@ var drawerWidth = 240;
5229
5431
  var MainLayout = () => {
5230
5432
  const { clientContext } = useApp();
5231
5433
  const logout = useLogout_default();
5434
+ const navigate = useNavigate4();
5232
5435
  const isDrawerOpen = useIsDrawerOpen();
5233
5436
  const dispatch = useAppDispatch();
5234
5437
  const theme = useTheme5();
@@ -5272,7 +5475,7 @@ var MainLayout = () => {
5272
5475
  },
5273
5476
  children: /* @__PURE__ */ jsxs20(Toolbar, { children: [
5274
5477
  /* @__PURE__ */ jsx26(
5275
- IconButton6,
5478
+ IconButton8,
5276
5479
  {
5277
5480
  color: "inherit",
5278
5481
  "aria-label": "open drawer",
@@ -5294,8 +5497,9 @@ var MainLayout = () => {
5294
5497
  ),
5295
5498
  /* @__PURE__ */ jsx26(Typography13, { variant: "h6", noWrap: true, component: "div", sx: { flexGrow: 1 }, children: isDrawerOpen ? "" : clientContext.config.title }),
5296
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, {}) }),
5297
5501
  /* @__PURE__ */ jsx26(
5298
- Button9,
5502
+ Button8,
5299
5503
  {
5300
5504
  color: "inherit",
5301
5505
  onClick: logout,
@@ -5318,8 +5522,9 @@ var MainLayout = () => {
5318
5522
  "& .MuiDrawer-paper": {
5319
5523
  width: drawerWidth,
5320
5524
  boxSizing: "border-box",
5321
- overflowX: "hidden",
5322
- overflowY: "auto"
5525
+ overflow: "hidden",
5526
+ display: "flex",
5527
+ flexDirection: "column"
5323
5528
  }
5324
5529
  },
5325
5530
  children: [
@@ -5331,6 +5536,8 @@ var MainLayout = () => {
5331
5536
  alignItems: "center",
5332
5537
  justifyContent: "flex-start",
5333
5538
  px: 1,
5539
+ flexShrink: 0,
5540
+ // el logo queda fijo: solo scrollea la lista del menú
5334
5541
  fontSize: "1.3rem",
5335
5542
  minHeight: theme.mixins.toolbar.minHeight,
5336
5543
  gap: 1.5
@@ -5389,7 +5596,7 @@ var MainLayout = () => {
5389
5596
  var MainLayout_default = MainLayout;
5390
5597
 
5391
5598
  // src/components/SessionExpiredMessage.tsx
5392
- import { Box as Box16, Typography as Typography14, Button as Button10, Modal } from "@mui/material";
5599
+ import { Box as Box16, Typography as Typography14, Button as Button9, Modal } from "@mui/material";
5393
5600
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
5394
5601
  var style = {
5395
5602
  position: "absolute",
@@ -5432,7 +5639,7 @@ var SessionExpiredMessage = () => {
5432
5639
  children: /* @__PURE__ */ jsxs21(Box16, { sx: style, children: [
5433
5640
  /* @__PURE__ */ jsx27(Typography14, { id: "session-expired-modal-title", variant: "h6", component: "h2", gutterBottom: true, children: "\xA1Sesi\xF3n Expirada!" }),
5434
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." }),
5435
- /* @__PURE__ */ jsx27(Button10, { variant: "contained", color: "primary", onClick: handleClose, children: "Ir al Login" })
5642
+ /* @__PURE__ */ jsx27(Button9, { variant: "contained", color: "primary", onClick: handleClose, children: "Ir al Login" })
5436
5643
  ] })
5437
5644
  }
5438
5645
  );
@@ -5473,13 +5680,24 @@ var LogoutPage = () => {
5473
5680
  var LogoutPage_default = LogoutPage;
5474
5681
 
5475
5682
  // src/pages/LoginPage.tsx
5476
- import { useState as useState18 } from "react";
5477
- import { Button as Button11, TextField as TextField2, Typography as Typography16, Alert as Alert3, AlertTitle, Box as Box18, 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
5478
5696
  import { useForm, Controller } from "react-hook-form";
5479
5697
  import { zodResolver } from "@hookform/resolvers/zod";
5480
5698
  import * as z from "zod";
5481
5699
  import { useDispatch as useDispatch4 } from "react-redux";
5482
- import { Fragment as Fragment5, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
5700
+ import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
5483
5701
  var loginSchema = z.object({
5484
5702
  username: z.string().min(1, "El usuario es requerido"),
5485
5703
  // Añadir validación básica
@@ -5487,9 +5705,12 @@ var loginSchema = z.object({
5487
5705
  // Añadir validación básica
5488
5706
  });
5489
5707
  var LoginPage = () => {
5490
- const [loginError, setLoginError] = useState18(null);
5491
- const [isSubmitting, setIsSubmitting] = useState18(false);
5492
- 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();
5493
5714
  const dispatch = useDispatch4();
5494
5715
  const {
5495
5716
  control,
@@ -5521,6 +5742,7 @@ var LoginPage = () => {
5521
5742
  const errorText = "Credenciales incorrectas o error al iniciar sesi\xF3n";
5522
5743
  throw new Error(errorText);
5523
5744
  }
5745
+ avisarCierreVoluntario(false);
5524
5746
  setIsLoggedIn(true);
5525
5747
  await dispatch(fetchClientContext()).unwrap();
5526
5748
  console.log("Login exitoso y clientContext cargado.");
@@ -5560,58 +5782,77 @@ var LoginPage = () => {
5560
5782
  }
5561
5783
  ) }),
5562
5784
  /* @__PURE__ */ jsx30(Typography16, { variant: "h5", align: "center", gutterBottom: true, sx: { fontWeight: "bold", color: "primary.main" }, children: "Iniciar Sesi\xF3n" }),
5563
- /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-4", children: [
5564
- /* @__PURE__ */ jsxs23("div", { children: [
5565
- /* @__PURE__ */ jsx30("label", { htmlFor: "username", children: /* @__PURE__ */ jsx30(Typography16, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Usuario" }) }),
5566
- /* @__PURE__ */ jsx30(
5567
- Controller,
5568
- {
5569
- name: "username",
5570
- control,
5571
- render: ({ field }) => /* @__PURE__ */ jsx30(
5572
- TextField2,
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,
5573
5801
  {
5574
- ...field,
5575
- type: "text",
5576
- placeholder: "Ingresa tu usuario",
5577
- fullWidth: true,
5578
- margin: "normal",
5579
- error: !!errors.username,
5580
- helperText: errors.username?.message,
5581
- 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
+ )
5582
5819
  }
5583
5820
  )
5584
- }
5585
- )
5586
- ] }),
5587
- /* @__PURE__ */ jsxs23("div", { children: [
5588
- /* @__PURE__ */ jsx30("label", { htmlFor: "password", children: /* @__PURE__ */ jsx30(Typography16, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Contrase\xF1a" }) }),
5589
- /* @__PURE__ */ jsx30(
5590
- Controller,
5591
- {
5592
- name: "password",
5593
- control,
5594
- render: ({ field }) => /* @__PURE__ */ jsx30(
5595
- 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,
5596
5826
  {
5597
- ...field,
5598
- type: "password",
5599
- placeholder: "Ingresa tu contrase\xF1a",
5600
- fullWidth: true,
5601
- margin: "normal",
5602
- error: !!errors.password,
5603
- helperText: errors.password?.message,
5604
- 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
+ )
5605
5844
  }
5606
5845
  )
5607
- }
5608
- )
5609
- ] }),
5610
- /* @__PURE__ */ jsx30(Button11, { type: "submit", fullWidth: true, variant: "contained", color: "primary", disabled: isSubmitting, sx: { mt: 2 }, children: isSubmitting ? /* @__PURE__ */ jsxs23(Fragment5, { children: [
5611
- /* @__PURE__ */ jsx30(CircularProgress6, { size: 20, sx: { mr: 1 } }),
5612
- "Iniciando sesi\xF3n..."
5613
- ] }) : "Iniciar Sesi\xF3n" })
5614
- ] }),
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." }),
5615
5856
  loginError && /* @__PURE__ */ jsxs23(Alert3, { severity: "error", sx: { mt: 2, maxHeight: "200px" }, children: [
5616
5857
  " ",
5617
5858
  /* @__PURE__ */ jsx30(AlertTitle, { children: "Error" }),
@@ -5626,11 +5867,11 @@ var LoginPage = () => {
5626
5867
  var LoginPage_default = LoginPage;
5627
5868
 
5628
5869
  // src/components/ProcedureForm.tsx
5629
- 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";
5630
5871
  import { useParams } from "react-router-dom";
5631
5872
  import {
5632
- TextField as TextField3,
5633
- Button as Button12,
5873
+ TextField as TextField4,
5874
+ Button as Button11,
5634
5875
  FormControl as FormControl3,
5635
5876
  InputLabel as InputLabel2,
5636
5877
  Select as Select2,
@@ -5673,17 +5914,17 @@ var ProcedureForm = () => {
5673
5914
  const { procedureName } = useParams();
5674
5915
  const { clientContext } = useApp();
5675
5916
  const { callApi, loading: apiLoading, error: apiError } = useApiCall();
5676
- const [validationErrors, setValidationErrors] = useState19({});
5677
- const [successMessage, setSuccessMessage] = useState19(null);
5678
- const [executionLog, setExecutionLog] = useState19([]);
5679
- const [procedureResultData, setProcedureResultData] = useState19(null);
5680
- 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(() => {
5681
5922
  if (procedureName && clientContext?.procedure) {
5682
5923
  return clientContext.procedure[procedureName];
5683
5924
  }
5684
5925
  return void 0;
5685
5926
  }, [procedureName, clientContext]);
5686
- const [formValues, setFormValues] = useState19(() => {
5927
+ const [formValues, setFormValues] = useState20(() => {
5687
5928
  if (!procedure) return {};
5688
5929
  const initialValues = {};
5689
5930
  procedure.parameters.forEach((param) => {
@@ -5912,13 +6153,13 @@ var ProcedureForm = () => {
5912
6153
  case "tsrange":
5913
6154
  case "time_range":
5914
6155
  case "daterange":
5915
- return /* @__PURE__ */ jsx32(TextField3, { type: "text", value: formValues[param.name] || "", ...commonProps });
6156
+ return /* @__PURE__ */ jsx32(TextField4, { type: "text", value: formValues[param.name] || "", ...commonProps });
5916
6157
  case "decimal":
5917
6158
  case "double":
5918
- return /* @__PURE__ */ jsx32(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 });
5919
6160
  case "integer":
5920
6161
  case "bigint":
5921
- return /* @__PURE__ */ jsx32(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 });
5922
6163
  case "boolean":
5923
6164
  return /* @__PURE__ */ jsx32(
5924
6165
  FormControlLabel3,
@@ -5941,10 +6182,10 @@ var ProcedureForm = () => {
5941
6182
  case "date":
5942
6183
  case "timestamp":
5943
6184
  case "time":
5944
- return /* @__PURE__ */ jsx32(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" } } });
5945
6186
  case "jsonb":
5946
6187
  case "jsona":
5947
- return /* @__PURE__ */ jsx32(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" } } });
5948
6189
  case "bytea":
5949
6190
  return /* @__PURE__ */ jsxs25(FormControl3, { fullWidth: true, margin: "dense", error: hasError, required: commonProps.required, children: [
5950
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 }),
@@ -5965,10 +6206,10 @@ var ProcedureForm = () => {
5965
6206
  (param.description || errorMessage) && /* @__PURE__ */ jsx32(FormHelperText2, { sx: { fontSize: "0.7rem" }, children: errorMessage || param.description })
5966
6207
  ] });
5967
6208
  default:
5968
- return /* @__PURE__ */ jsx32(TextField3, { type: "text", value: formValues[param.name] || "", ...commonProps });
6209
+ return /* @__PURE__ */ jsx32(TextField4, { type: "text", value: formValues[param.name] || "", ...commonProps });
5969
6210
  }
5970
6211
  }, [formValues, handleChange, apiLoading, validationErrors]);
5971
- const ResultDisplayContent = useMemo12(() => {
6212
+ const ResultDisplayContent = useMemo13(() => {
5972
6213
  if (!procedureResultData || !procedure?.resultOk) return null;
5973
6214
  const ComponentToRender = resultsOk[procedure.resultOk];
5974
6215
  if (ComponentToRender) {
@@ -6032,7 +6273,7 @@ var ProcedureForm = () => {
6032
6273
  /* @__PURE__ */ jsxs25("form", { onSubmit: handleSubmit, children: [
6033
6274
  procedure.parameters.map((param) => /* @__PURE__ */ jsx32("div", { children: renderInputField(param) }, param.name)),
6034
6275
  /* @__PURE__ */ jsx32(
6035
- Button12,
6276
+ Button11,
6036
6277
  {
6037
6278
  type: "submit",
6038
6279
  variant: "contained",
@@ -6093,10 +6334,10 @@ import { PersistGate } from "redux-persist/integration/react";
6093
6334
 
6094
6335
  // src/components/PrivateRoute.tsx
6095
6336
  import { useEffect as useEffect11 } from "react";
6096
- import { Navigate as Navigate2, useLocation as useLocation3 } from "react-router-dom";
6097
- import { Fragment as Fragment6, jsx as jsx33 } 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";
6098
6339
  var PrivateRoute = ({ children }) => {
6099
- const location = useLocation3();
6340
+ const location = useLocation4();
6100
6341
  const dispatch = useAppDispatch();
6101
6342
  const clientContextStatus = useClientContextStatus();
6102
6343
  const isAuthenticated = clientContextStatus === "succeeded";
@@ -6104,7 +6345,7 @@ var PrivateRoute = ({ children }) => {
6104
6345
  console.log(`[PrivateRoute] Render: Path=${location.pathname}, AuthStatus=${clientContextStatus}, IsAuthenticated=${isAuthenticated}, IsLoadingAuth=${isLoadingAuth}`);
6105
6346
  useEffect11(() => {
6106
6347
  console.log(`[PrivateRoute] Effect: Path=${location.pathname}, AuthStatus=${clientContextStatus}, IsAuthenticated=${isAuthenticated}, IsLoadingAuth=${isLoadingAuth}`);
6107
- if (!isAuthenticated && !isLoadingAuth && location.pathname !== "/login") {
6348
+ if (!isAuthenticated && !isLoadingAuth && location.pathname !== "/login" && !huboCierreVoluntario()) {
6108
6349
  const path = location.pathname + location.search + location.hash;
6109
6350
  console.log(`[PrivateRoute] ACTION: Dispatching setRedirectPath(${path})`);
6110
6351
  dispatch(setRedirectPath(path));
@@ -6125,29 +6366,33 @@ var PrivateRoute = ({ children }) => {
6125
6366
  return /* @__PURE__ */ jsx33(Navigate2, { to: "/login", replace: true });
6126
6367
  }
6127
6368
  console.log(`[PrivateRoute] User authenticated, rendering children.`);
6128
- return /* @__PURE__ */ jsx33(Fragment6, { children });
6369
+ return /* @__PURE__ */ jsx33(Fragment7, { children });
6129
6370
  };
6130
6371
  var PrivateRoute_default = PrivateRoute;
6131
6372
 
6132
6373
  // src/components/InitialRedirectHandler.tsx
6133
- import { useEffect as useEffect12, useRef as useRef9 } from "react";
6134
- 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";
6135
6376
  var InitialRedirectHandler = () => {
6136
- const location = useLocation4();
6137
- const navigate = useNavigate4();
6377
+ const location = useLocation5();
6378
+ const navigate = useNavigate5();
6138
6379
  const dispatch = useAppDispatch();
6139
6380
  const redirectPath = useRedirectPath();
6140
6381
  const clientContextStatus = useClientContextStatus();
6382
+ const username = useAppSelector((state) => state.clientContext.username);
6383
+ const redirectUsername = useAppSelector((state) => state.router.redirectUsername);
6141
6384
  useEffect12(() => {
6142
6385
  dispatch(setCurrentPath(location.pathname + location.search));
6143
6386
  }, [location, dispatch]);
6144
- const navegandoRef = useRef9(false);
6387
+ const navegandoRef = useRef10(false);
6145
6388
  useEffect12(() => {
6146
6389
  navegandoRef.current = false;
6147
6390
  }, [location.pathname]);
6148
6391
  useEffect12(() => {
6149
6392
  if (clientContextStatus === "succeeded") {
6150
- if (redirectPath && redirectPath !== "/login") {
6393
+ if (redirectPath && redirectPath !== "/login" && redirectUsername && redirectUsername !== username) {
6394
+ dispatch(setRedirectPath(null));
6395
+ } else if (redirectPath && redirectPath !== "/login") {
6151
6396
  console.log("Redirigiendo a la ruta guardada:", redirectPath);
6152
6397
  navegandoRef.current = true;
6153
6398
  navigate(redirectPath);
@@ -6157,15 +6402,15 @@ var InitialRedirectHandler = () => {
6157
6402
  navigate("/");
6158
6403
  }
6159
6404
  }
6160
- }, [clientContextStatus, redirectPath, location.pathname, navigate, dispatch]);
6405
+ }, [clientContextStatus, redirectPath, redirectUsername, username, location.pathname, navigate, dispatch]);
6161
6406
  return null;
6162
6407
  };
6163
6408
  var InitialRedirectHandler_default = InitialRedirectHandler;
6164
6409
 
6165
6410
  // src/pages/GenericDataGridPage.tsx
6166
- import React26, { useState as useState20, useCallback as useCallback16, useMemo as useMemo13 } from "react";
6167
- import { useParams as useParams2, useLocation as useLocation5 } from "react-router-dom";
6168
- import { Box as Box21, Alert as Alert5, Typography as Typography19, 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";
6169
6414
  import CloseIcon2 from "@mui/icons-material/Close";
6170
6415
  import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
6171
6416
  var GenericDataGridPage = ({
@@ -6175,11 +6420,11 @@ var GenericDataGridPage = ({
6175
6420
  }) => {
6176
6421
  const { tableName: urlTableName } = useParams2();
6177
6422
  const effectiveTableName = initialTableName || urlTableName;
6178
- const [tabs, setTabs] = useState20([]);
6179
- const [activeTabIdx, setActiveTabIdx] = useState20(0);
6423
+ const [tabs, setTabs] = useState21([]);
6424
+ const [activeTabIdx, setActiveTabIdx] = useState21(0);
6180
6425
  const ffKey = JSON.stringify(initialFixedFields);
6181
- const location = useLocation5();
6182
- 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]);
6183
6428
  const fixedFieldsIniciales = initialFixedFields.length > 0 ? initialFixedFields : parametros.fixedFields ?? [];
6184
6429
  const parametrosKey = JSON.stringify(parametros);
6185
6430
  React26.useEffect(() => {
@@ -6226,7 +6471,7 @@ var GenericDataGridPage = ({
6226
6471
  {
6227
6472
  label: /* @__PURE__ */ jsxs26(Box21, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
6228
6473
  /* @__PURE__ */ jsx34(Typography19, { variant: "caption", sx: { fontWeight: "bold" }, children: tab.label }),
6229
- index !== 0 && /* @__PURE__ */ jsx34(IconButton7, { size: "small", onClick: (e) => handleCloseTab(index, e), children: /* @__PURE__ */ jsx34(CloseIcon2, { fontSize: "inherit" }) })
6474
+ index !== 0 && /* @__PURE__ */ jsx34(IconButton9, { size: "small", onClick: (e) => handleCloseTab(index, e), children: /* @__PURE__ */ jsx34(CloseIcon2, { fontSize: "inherit" }) })
6230
6475
  ] })
6231
6476
  },
6232
6477
  tab.id
@@ -6258,27 +6503,27 @@ var GenericDataGridPage = ({
6258
6503
  var GenericDataGridPage_default = GenericDataGridPage;
6259
6504
 
6260
6505
  // src/pages/PantallaDeRuta.tsx
6261
- import { useMemo as useMemo14 } from "react";
6506
+ import { useMemo as useMemo15 } from "react";
6262
6507
  import { useSearchParams } from "react-router-dom";
6263
6508
  import { Box as Box22 } from "@mui/material";
6264
6509
  import { jsx as jsx35 } from "react/jsx-runtime";
6265
6510
  var PantallaDeRuta = ({ screenName }) => {
6266
6511
  const [busqueda] = useSearchParams();
6267
- const params = useMemo14(() => Object.fromEntries(busqueda.entries()), [busqueda]);
6512
+ const params = useMemo15(() => Object.fromEntries(busqueda.entries()), [busqueda]);
6268
6513
  const Pantalla = wScreens[screenName];
6269
6514
  return /* @__PURE__ */ jsx35(Box22, { sx: { pt: 3, pl: 4 }, children: Pantalla ? /* @__PURE__ */ jsx35(Pantalla, { screenName, params }) : /* @__PURE__ */ jsx35(FallbackWScreen, { screenName }) });
6270
6515
  };
6271
6516
 
6272
6517
  // src/pages/MenuTablePage.tsx
6273
- import { useMemo as useMemo15 } from "react";
6274
- import { useLocation as useLocation6, useParams as useParams3 } from "react-router-dom";
6518
+ import { useMemo as useMemo16 } from "react";
6519
+ import { useLocation as useLocation7, useParams as useParams3 } from "react-router-dom";
6275
6520
  import { Box as Box23, Alert as Alert6, Typography as Typography20 } from "@mui/material";
6276
6521
  import { jsx as jsx36, jsxs as jsxs27 } from "react/jsx-runtime";
6277
6522
  var MenuTablePage = () => {
6278
6523
  const { "*": menuName } = useParams3();
6279
6524
  const { clientContext } = useApp();
6280
- const { search } = useLocation6();
6281
- const { currentTableName, currentFixedFields, parametros } = useMemo15(() => {
6525
+ const { search } = useLocation7();
6526
+ const { currentTableName, currentFixedFields, parametros } = useMemo16(() => {
6282
6527
  let tableName = void 0;
6283
6528
  let fixedFields = [];
6284
6529
  let parametros2 = {};
@@ -6316,8 +6561,8 @@ var MenuTablePage = () => {
6316
6561
  var MenuTablePage_default = MenuTablePage;
6317
6562
 
6318
6563
  // src/puente/PantallaBP.tsx
6319
- import { useEffect as useEffect15, useRef as useRef10, useState as useState21 } from "react";
6320
- import { Navigate as Navigate3, useLocation as useLocation7, useParams as useParams4 } from "react-router-dom";
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";
6321
6566
  import { Alert as Alert7, Box as Box25, CircularProgress as CircularProgress8 } from "@mui/material";
6322
6567
 
6323
6568
  // src/puente/clienteBP.ts
@@ -6520,13 +6765,6 @@ function resolverPantallaBP(myOwn, nombre, contenedor) {
6520
6765
  return null;
6521
6766
  }
6522
6767
 
6523
- // src/puente/habilitacion.ts
6524
- var habilitado = false;
6525
- var habilitarPuenteBP = () => {
6526
- habilitado = true;
6527
- };
6528
- var puenteBPHabilitado = () => habilitado;
6529
-
6530
6768
  // src/puente/PantallaBP.tsx
6531
6769
  import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
6532
6770
  function buscarItemDeMenu(items, tipo) {
@@ -6541,10 +6779,10 @@ function buscarItemDeMenu(items, tipo) {
6541
6779
  }
6542
6780
  var PantallaBP = ({ nombre, direccion }) => {
6543
6781
  const { clientContext } = useApp();
6544
- const [cargando, setCargando] = useState21(true);
6545
- const [error, setError] = useState21(null);
6546
- const [noExiste, setNoExiste] = useState21(false);
6547
- 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);
6548
6786
  const menu = clientContext.menu;
6549
6787
  useEffect15(() => {
6550
6788
  const elemento = contenedor.current;
@@ -6590,14 +6828,14 @@ var PantallaBP = ({ nombre, direccion }) => {
6590
6828
  };
6591
6829
  var PantallaDeRespaldo = () => {
6592
6830
  const { screenName = "" } = useParams4();
6593
- const { search } = useLocation7();
6831
+ const { search } = useLocation8();
6594
6832
  if (!puenteBPHabilitado()) {
6595
6833
  return /* @__PURE__ */ jsx38(Box25, { sx: { p: 3 }, children: /* @__PURE__ */ jsx38(FallbackWScreen, { screenName }) });
6596
6834
  }
6597
6835
  return /* @__PURE__ */ jsx38(Navigate3, { to: rutaDePantallaBP(screenName, search), replace: true });
6598
6836
  };
6599
6837
  var PaginaBP = () => {
6600
- const { hash, search } = useLocation7();
6838
+ const { hash, search } = useLocation8();
6601
6839
  const direccion = hash || search;
6602
6840
  const nombre = new URLSearchParams(direccion.replace(/^[#?]/, "")).get("w");
6603
6841
  if (!puenteBPHabilitado()) return /* @__PURE__ */ jsx38(Navigate3, { to: "/", replace: true });
@@ -6610,14 +6848,14 @@ var PaginaBP = () => {
6610
6848
  };
6611
6849
 
6612
6850
  // src/puente/GrillaBP.tsx
6613
- 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";
6614
6852
  import { useParams as useParams5 } from "react-router-dom";
6615
6853
  import { Alert as Alert8, Box as Box26, CircularProgress as CircularProgress9 } from "@mui/material";
6616
6854
  import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
6617
6855
  var GrillaBP = ({ tabla, fixedFields }) => {
6618
- const contenedor = useRef11(null);
6619
- const [cargando, setCargando] = useState22(true);
6620
- const [error, setError] = useState22(null);
6856
+ const contenedor = useRef12(null);
6857
+ const [cargando, setCargando] = useState23(true);
6858
+ const [error, setError] = useState23(null);
6621
6859
  const clave = JSON.stringify(fixedFields ?? []);
6622
6860
  useEffect16(() => {
6623
6861
  const elemento = contenedor.current;
@@ -6657,7 +6895,7 @@ var PaginaGrillaBP = () => {
6657
6895
 
6658
6896
  // src/puente/CapturaBP.tsx
6659
6897
  import { useEffect as useEffect17 } from "react";
6660
- import { useNavigate as useNavigate5 } from "react-router-dom";
6898
+ import { useNavigate as useNavigate6 } from "react-router-dom";
6661
6899
 
6662
6900
  // src/puente/capturaDeNavegacion.ts
6663
6901
  function apiDeNavegacion() {
@@ -6702,16 +6940,128 @@ function capturarNavegacionBP(urlBackend, navegar) {
6702
6940
 
6703
6941
  // src/puente/CapturaBP.tsx
6704
6942
  var CapturaBP = () => {
6705
- const navigate = useNavigate5();
6943
+ const navigate = useNavigate6();
6706
6944
  useEffect17(() => capturarNavegacionBP(envConfig.backendUrl, navigate), [navigate]);
6707
6945
  return null;
6708
6946
  };
6709
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
+
6710
7060
  // src/App.tsx
6711
7061
  import typeStore6 from "type-store";
6712
- import { Fragment as Fragment7, jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
7062
+ import { Fragment as Fragment8, jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
6713
7063
  var LocationTracker = () => {
6714
- const location = useLocation8();
7064
+ const location = useLocation9();
6715
7065
  const dispatch = useAppDispatch();
6716
7066
  useEffect18(() => {
6717
7067
  dispatch(setCurrentPath(location.pathname + location.search));
@@ -6719,45 +7069,46 @@ var LocationTracker = () => {
6719
7069
  return null;
6720
7070
  };
6721
7071
  function FrontendPlusReactRoutes({ myRoutes, myUnloggedRoutes, layout: Layout = MainLayout_default, unloggedLayout: UnloggedLayout }) {
6722
- return /* @__PURE__ */ jsxs30(Fragment7, { children: [
6723
- /* @__PURE__ */ jsx40(InitialRedirectHandler_default, {}),
6724
- puenteBPHabilitado() && /* @__PURE__ */ jsx40(CapturaBP, {}),
6725
- /* @__PURE__ */ jsxs30(Routes, { children: [
6726
- /* @__PURE__ */ jsxs30(Route, { element: UnloggedLayout ? /* @__PURE__ */ jsx40(UnloggedLayout, {}) : /* @__PURE__ */ jsx40(Outlet2, {}), children: [
6727
- /* @__PURE__ */ jsx40(Route, { path: "/login", element: /* @__PURE__ */ jsx40(LoginPage_default, {}) }),
6728
- /* @__PURE__ */ jsx40(Route, { path: "/logout", element: /* @__PURE__ */ jsx40(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, {}) }),
6729
7079
  myUnloggedRoutes
6730
7080
  ] }),
6731
- /* @__PURE__ */ jsxs30(Route, { element: /* @__PURE__ */ jsx40(PrivateRoute_default, { children: /* @__PURE__ */ jsx40(Layout, {}) }), children: [
6732
- /* @__PURE__ */ jsx40(Route, { path: "/", element: /* @__PURE__ */ jsx40(PantallaInicial_default, {}) }),
6733
- /* @__PURE__ */ jsx40(Route, { path: "/table/:tableName", element: /* @__PURE__ */ jsx40(GenericDataGridPage_default, {}) }),
6734
- /* @__PURE__ */ jsx40(Route, { path: "/menu/*", element: /* @__PURE__ */ jsx40(MenuTablePage_default, {}) }),
6735
- /* @__PURE__ */ jsx40(Route, { path: "/procedures/:procedureName", element: /* @__PURE__ */ jsx40(ProcedureForm_default, {}) }),
6736
- Object.keys(wScreens).map((screenName) => /* @__PURE__ */ jsx40(
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(
6737
7088
  Route,
6738
7089
  {
6739
7090
  path: `/wScreens/${screenName}`,
6740
- element: /* @__PURE__ */ jsx40(PantallaDeRuta, { screenName })
7091
+ element: /* @__PURE__ */ jsx41(PantallaDeRuta, { screenName })
6741
7092
  },
6742
7093
  screenName
6743
7094
  )),
6744
- /* @__PURE__ */ jsx40(Route, { path: "/wScreens-fallback/:screenName", element: /* @__PURE__ */ jsx40(PantallaDeRespaldo, {}) }),
6745
- puenteBPHabilitado() && /* @__PURE__ */ jsx40(Route, { path: "/bp/menu", element: /* @__PURE__ */ jsx40(PaginaBP, {}) }),
6746
- puenteBPHabilitado() && /* @__PURE__ */ jsx40(Route, { path: "/bp/table/:tableName", element: /* @__PURE__ */ jsx40(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, {}) }),
6747
7098
  myRoutes,
6748
- /* @__PURE__ */ jsx40(Route, { path: "*", element: /* @__PURE__ */ jsx40("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" }) })
6749
7100
  ] }),
6750
- /* @__PURE__ */ jsx40(Route, { path: "*", element: /* @__PURE__ */ jsx40(Navigate4, { to: "/", replace: true }) })
7101
+ /* @__PURE__ */ jsx41(Route, { path: "*", element: /* @__PURE__ */ jsx41(Navigate4, { to: "/", replace: true }) })
6751
7102
  ] })
6752
7103
  ] });
6753
7104
  }
6754
7105
  var FrontendPlusProviders = ({ children, publicPaths }) => {
6755
- return /* @__PURE__ */ jsx40(Provider, { store, children: /* @__PURE__ */ jsx40(PersistGate, { loading: null, persistor, children: /* @__PURE__ */ jsxs30(BrowserRouter, { basename: rutaBase, children: [
6756
- /* @__PURE__ */ jsx40(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, {}),
6757
7108
  " ",
6758
- /* @__PURE__ */ jsx40(AppProvider, { publicPaths, children: /* @__PURE__ */ jsxs30(SnackbarProvider, { children: [
7109
+ /* @__PURE__ */ jsx41(AppProvider, { publicPaths, children: /* @__PURE__ */ jsxs31(SnackbarProvider, { children: [
6759
7110
  children,
6760
- /* @__PURE__ */ jsx40(SessionExpiredMessage_default, {})
7111
+ /* @__PURE__ */ jsx41(SessionExpiredMessage_default, {})
6761
7112
  ] }) })
6762
7113
  ] }) }) });
6763
7114
  };
@@ -6769,9 +7120,10 @@ var App = ({
6769
7120
  myClientSides,
6770
7121
  myResultsOk,
6771
7122
  myConditions,
6772
- puenteBP
7123
+ puenteBP,
7124
+ recuperarClave
6773
7125
  }) => {
6774
- useMemo16(() => {
7126
+ useMemo17(() => {
6775
7127
  if (myClientSides) {
6776
7128
  extendClientSides(myClientSides);
6777
7129
  }
@@ -6787,7 +7139,8 @@ var App = ({
6787
7139
  if (puenteBP) {
6788
7140
  habilitarPuenteBP();
6789
7141
  }
6790
- }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP]);
7142
+ configurarRecuperarClave(recuperarClave);
7143
+ }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP, recuperarClave]);
6791
7144
  useEffect18(() => {
6792
7145
  typeStore6.messages = typeStore6.i18n.messages.es;
6793
7146
  typeStore6.locale = typeStore6.i18n.locale.es;
@@ -6797,11 +7150,11 @@ var App = ({
6797
7150
  link.href = `${envConfig.backendUrl}/img/logo-128.png`;
6798
7151
  document.getElementsByTagName("head")[0].appendChild(link);
6799
7152
  }, []);
6800
- const safePaths = React33.useMemo(() => {
7153
+ const safePaths = React34.useMemo(() => {
6801
7154
  const extracted = extractPathsFromRoutes(myUnloggedRoutes);
6802
7155
  return ["/login", "/logout", ...extracted];
6803
7156
  }, [myUnloggedRoutes]);
6804
- return /* @__PURE__ */ jsx40(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ jsx40(
7157
+ return /* @__PURE__ */ jsx41(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ jsx41(
6805
7158
  FrontendPlusReactRoutes,
6806
7159
  {
6807
7160
  myRoutes,