frontend-plus-react 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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;
@@ -1382,8 +1398,12 @@ function BotonDetalle({ row, rowId, detailTable, deshabilitado }) {
1382
1398
  IconButton,
1383
1399
  {
1384
1400
  size: "small",
1401
+ sx: { width: 24, height: 24, p: 0 },
1385
1402
  disabled: deshabilitado,
1386
1403
  onClick: (e) => alternarDetalle(row, rowId, detailTable, e.ctrlKey || e.metaKey),
1404
+ onMouseDown: (e) => {
1405
+ if (e.button === 1) e.preventDefault();
1406
+ },
1387
1407
  onAuxClick: (e) => {
1388
1408
  if (e.button === 1) alternarDetalle(row, rowId, detailTable, true);
1389
1409
  },
@@ -2005,9 +2025,10 @@ var defaultColumnHeaderCellRenderer = (props, fieldDef, ocultarColumna) => {
2005
2025
  {
2006
2026
  variant: "body2",
2007
2027
  sx: {
2008
- fontWeight: "bold",
2028
+ fontWeight: 500,
2029
+ color: "#37474f",
2009
2030
  textDecoration: isPrimaryKey ? "underline" : "none",
2010
- fontSize: "0.930rem",
2031
+ fontSize: "0.85rem",
2011
2032
  textAlign: "left",
2012
2033
  flexGrow: 1,
2013
2034
  overflow: "hidden",
@@ -2033,22 +2054,20 @@ var ActionsColumnHeaderCell = ({
2033
2054
  allowInsert && /* @__PURE__ */ jsx6(
2034
2055
  IconButton2,
2035
2056
  {
2036
- color: "inherit",
2037
2057
  onClick: () => handleAddRow(),
2038
2058
  size: "small",
2039
2059
  title: "A\xF1adir registro",
2040
- sx: { p: 0.25 },
2041
- children: /* @__PURE__ */ jsx6(AddIcon, { sx: { fontSize: 18, color: "success.main" } })
2060
+ sx: { p: 0.25, color: "grey.800", "&:hover": { color: "success.main" } },
2061
+ children: /* @__PURE__ */ jsx6(AddIcon, { sx: { fontSize: 18 } })
2042
2062
  }
2043
2063
  ),
2044
2064
  /* @__PURE__ */ jsx6(
2045
2065
  IconButton2,
2046
2066
  {
2047
- color: "inherit",
2048
2067
  onClick: toggleFilterVisibility,
2049
2068
  size: "small",
2050
2069
  title: isFilterRowVisible ? "Ocultar filtros" : "Mostrar filtros",
2051
- sx: { p: 0.25 },
2070
+ sx: { p: 0.25, color: "grey.800" },
2052
2071
  children: isFilterRowVisible ? /* @__PURE__ */ jsx6(SearchOffIcon, { sx: { fontSize: 18 } }) : /* @__PURE__ */ jsx6(SearchIcon, { sx: { fontSize: 18 } })
2053
2072
  }
2054
2073
  ),
@@ -2058,13 +2077,13 @@ var ActionsColumnHeaderCell = ({
2058
2077
  onClick: (event) => handleDataGridOptions(event),
2059
2078
  size: "small",
2060
2079
  title: "Opciones de esta tabla",
2061
- sx: { p: 0.25 },
2080
+ sx: { p: 0.25, color: "grey.800" },
2062
2081
  children: /* @__PURE__ */ jsx6(MoreVertIcon, { sx: { fontSize: 20, variant: "filled", size: "small" } })
2063
2082
  }
2064
2083
  )
2065
2084
  ] });
2066
2085
  };
2067
- var detailColumnCellHeaderRenderer = (props, detailTable) => /* @__PURE__ */ jsx6(Tooltip2, { title: detailTable.label, children: /* @__PURE__ */ jsx6(Typography2, { variant: "caption", sx: { fontSize: "0.75rem", fontWeight: "bold" }, children: detailTable.abr.toUpperCase() }) });
2086
+ var detailColumnCellHeaderRenderer = (props, detailTable) => /* @__PURE__ */ jsx6(Tooltip2, { title: detailTable.label, children: /* @__PURE__ */ jsx6(Box3, { sx: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx6(Typography2, { variant: "caption", sx: { fontSize: "0.75rem", fontWeight: "bold" }, children: detailTable.abr.toUpperCase() }) }) });
2068
2087
 
2069
2088
  // src/components/grid/FilterInputRender.tsx
2070
2089
  import { InputBase as InputBase3, Box as Box4 } from "@mui/material";
@@ -2548,9 +2567,12 @@ var TypedField = ({ fieldDef, value, onChange, onCommit, disabled, isFilterMode
2548
2567
  "& .MuiOutlinedInput-root": {
2549
2568
  backgroundColor: "background.paper",
2550
2569
  height: FILTER_ROW_HEIGHT - 5,
2551
- minWidth: 50,
2570
+ minWidth: 30,
2552
2571
  fontSize: "0.8rem",
2553
2572
  padding: "0px"
2573
+ },
2574
+ "& .MuiOutlinedInput-input": {
2575
+ padding: "0 4px"
2554
2576
  }
2555
2577
  } : {
2556
2578
  "& .MuiOutlinedInput-root": {
@@ -2597,36 +2619,42 @@ function FilterInputRenderer({
2597
2619
  [columnKey]: { operator: newOp, value: formattedVal }
2598
2620
  }));
2599
2621
  };
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
- ] });
2622
+ return (
2623
+ // como en backend-plus, el operador es un símbolo chico y el campo se queda con el resto del ancho
2624
+ /* @__PURE__ */ jsxs10(Box4, { sx: { mt: 0, display: "flex", alignItems: "center", gap: "2px", width: "100%" }, children: [
2625
+ /* @__PURE__ */ jsx12(
2626
+ "select",
2627
+ {
2628
+ value: operator,
2629
+ onChange: (e) => handleFilterChange(e.target.value, val),
2630
+ style: {
2631
+ height: FILTER_ROW_HEIGHT - 5,
2632
+ width: 22,
2633
+ flexShrink: 0,
2634
+ appearance: "none",
2635
+ backgroundColor: "#CEF",
2636
+ border: "none",
2637
+ borderRadius: "4px",
2638
+ fontSize: "0.8rem",
2639
+ padding: 0,
2640
+ textAlign: "center",
2641
+ textAlignLast: "center",
2642
+ cursor: "pointer"
2643
+ },
2644
+ children: filterOperators.map((op) => /* @__PURE__ */ jsx12("option", { value: op.value, children: op.label }, op.value))
2645
+ }
2646
+ ),
2647
+ /* @__PURE__ */ jsx12(
2648
+ TypedField,
2649
+ {
2650
+ fieldDef,
2651
+ isFilterMode: true,
2652
+ value: val,
2653
+ onChange: (_name, newValue, _isValid) => handleFilterChange(operator, newValue)
2654
+ }
2655
+ )
2656
+ ] })
2657
+ );
2630
2658
  }
2631
2659
  return /* @__PURE__ */ jsx12(
2632
2660
  InputBase3,
@@ -2662,7 +2690,7 @@ var defaultColumnSummaryCellRenderer = (props, fixedFields) => {
2662
2690
  const { filters, setFilters } = row;
2663
2691
  if (valorFijoDe(fixedFields, column.key)) return null;
2664
2692
  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(
2693
+ return /* @__PURE__ */ jsx13(Box5, { sx: { position: "absolute", inset: 0, display: "flex", alignItems: "center", px: "2px" }, children: /* @__PURE__ */ jsx13(
2666
2694
  FilterInputRender_default,
2667
2695
  {
2668
2696
  columnKey: column.key,
@@ -2723,7 +2751,7 @@ var detailColumnCellSummaryRenderer = (_props) => null;
2723
2751
 
2724
2752
  // src/components/grid/renderers/cellRenderers.tsx
2725
2753
  import React11 from "react";
2726
- import { Box as Box8, Button as Button4, Tooltip as Tooltip4, useTheme as useTheme2 } from "@mui/material";
2754
+ import { Box as Box8, IconButton as IconButton5, Tooltip as Tooltip4, useTheme as useTheme2 } from "@mui/material";
2727
2755
  import ViewHeadlineIcon from "@mui/icons-material/ViewHeadline";
2728
2756
  import DeleteIcon from "@mui/icons-material/Delete";
2729
2757
  import AddIcon2 from "@mui/icons-material/Add";
@@ -2889,14 +2917,12 @@ var allColumnsCellRenderer = (props) => {
2889
2917
  }
2890
2918
  ];
2891
2919
  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,
2920
+ IconButton5,
2893
2921
  {
2894
- variant: "outlined",
2895
- color: actionDef.color,
2896
2922
  size: "small",
2897
2923
  onClick: () => actionDef.handler(row),
2898
2924
  title: actionDef.title,
2899
- sx: { width: 22, minWidth: 22, height: 22, "& .MuiButton-startIcon": { m: 0 } },
2925
+ sx: { width: 22, height: 22, p: 0, color: "grey.800", "&:hover": { color: `${actionDef.color}.main` } },
2900
2926
  children: /* @__PURE__ */ jsx16(actionDef.icon, { sx: { fontSize: 18 } })
2901
2927
  },
2902
2928
  actionDef.action
@@ -3306,20 +3332,36 @@ function campoDibujable(field) {
3306
3332
 
3307
3333
  // src/hooks/grid/useGridColumns.ts
3308
3334
  var estimateTextWidth = (text) => text.length * 8 + 16;
3309
- var calcularAnchosDeColumnas = (tableDefinition, filas) => {
3335
+ var contextoDeMedida;
3336
+ var anchoDeTitulo = (texto) => {
3337
+ if (contextoDeMedida === void 0) {
3338
+ try {
3339
+ contextoDeMedida = document.createElement("canvas").getContext("2d");
3340
+ } catch {
3341
+ contextoDeMedida = null;
3342
+ }
3343
+ }
3344
+ if (!contextoDeMedida) return texto.length * 9;
3345
+ contextoDeMedida.font = "bold 14.9px Roboto, Helvetica, Arial, sans-serif";
3346
+ return Math.ceil(contextoDeMedida.measureText(texto).width);
3347
+ };
3348
+ var EXTRA_TITULO = 8 + 20 + 4;
3349
+ var calcularAnchosDeColumnas = (tableDefinition, filas, anchoDisponible = Infinity) => {
3310
3350
  const anchos = /* @__PURE__ */ new Map();
3351
+ const anchosConTituloCompleto = /* @__PURE__ */ new Map();
3311
3352
  const muestra = filas.slice(0, 50);
3312
3353
  tableDefinition.fields.forEach((fieldDef) => {
3313
3354
  if (typeof fieldDef.width === "number" && fieldDef.width > 0) {
3314
3355
  anchos.set(fieldDef.name, fieldDef.width);
3356
+ anchosConTituloCompleto.set(fieldDef.name, fieldDef.width);
3315
3357
  return;
3316
3358
  }
3317
3359
  const headerLabel = fieldDef.label || fieldDef.name;
3318
3360
  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;
3361
+ let typeMinWidth = 80;
3362
+ if (type.includes("boolean") || type.includes("checkbox")) typeMinWidth = 80;
3363
+ else if (type.includes("date") || type.includes("timestamp")) typeMinWidth = 120;
3364
+ else if (type.includes("integer") || type.includes("numeric")) typeMinWidth = 90;
3323
3365
  const headerWidth = estimateTextWidth(headerLabel) + 20;
3324
3366
  const typer = typerFrom(fieldDef);
3325
3367
  const maxDataWidth = muestra.reduce((maxW, row) => {
@@ -3333,9 +3375,12 @@ var calcularAnchosDeColumnas = (tableDefinition, filas) => {
3333
3375
  }
3334
3376
  return Math.max(maxW, estimateTextWidth(texto));
3335
3377
  }, 0);
3336
- anchos.set(fieldDef.name, Math.min(Math.max(headerWidth, maxDataWidth, typeMinWidth), 500));
3378
+ const compacto = Math.min(Math.max(headerWidth, maxDataWidth, typeMinWidth), 500);
3379
+ anchos.set(fieldDef.name, compacto);
3380
+ anchosConTituloCompleto.set(fieldDef.name, Math.max(compacto, Math.min(anchoDeTitulo(cambiarGuionesBajosPorEspacios(headerLabel)) + EXTRA_TITULO, 500)));
3337
3381
  });
3338
- return anchos;
3382
+ const sumaCompleta = [...anchos.keys()].reduce((total, nombre) => total + (anchosConTituloCompleto.get(nombre) ?? 0), 0);
3383
+ return sumaCompleta <= anchoDisponible ? anchosConTituloCompleto : anchos;
3339
3384
  };
3340
3385
  var useGridColumns = ({
3341
3386
  tableDefinition,
@@ -3455,7 +3500,7 @@ import {
3455
3500
  DialogTitle as DialogTitle2,
3456
3501
  DialogContent as DialogContent2,
3457
3502
  DialogActions as DialogActions2,
3458
- Button as Button5,
3503
+ Button as Button4,
3459
3504
  FormControlLabel,
3460
3505
  Checkbox,
3461
3506
  Box as Box9,
@@ -3579,9 +3624,9 @@ var ImportDialog = ({ open, onClose, onImport, loading }) => {
3579
3624
  ] })
3580
3625
  ] }) }),
3581
3626
  /* @__PURE__ */ jsxs14(DialogActions2, { sx: { px: 3, py: 2 }, children: [
3582
- /* @__PURE__ */ jsx19(Button5, { onClick: onClose, disabled: loading, children: "Cancelar" }),
3627
+ /* @__PURE__ */ jsx19(Button4, { onClick: onClose, disabled: loading, children: "Cancelar" }),
3583
3628
  /* @__PURE__ */ jsx19(
3584
- Button5,
3629
+ Button4,
3585
3630
  {
3586
3631
  onClick: handleConfirm,
3587
3632
  variant: "contained",
@@ -3601,7 +3646,7 @@ import {
3601
3646
  DialogTitle as DialogTitle3,
3602
3647
  DialogContent as DialogContent3,
3603
3648
  DialogActions as DialogActions3,
3604
- Button as Button6,
3649
+ Button as Button5,
3605
3650
  FormControl as FormControl2,
3606
3651
  FormLabel,
3607
3652
  RadioGroup,
@@ -3809,14 +3854,14 @@ var ExportDialog = ({
3809
3854
  ] })
3810
3855
  ] }) }),
3811
3856
  /* @__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" })
3857
+ /* @__PURE__ */ jsx20(Button5, { onClick: onClose, disabled: isPreparing, children: "Cancelar" }),
3858
+ /* @__PURE__ */ jsx20(Button5, { onClick: handlePrepareExport, variant: "contained", disabled: isPreparing, children: "Descargar" })
3814
3859
  ] })
3815
3860
  ] });
3816
3861
  };
3817
3862
 
3818
3863
  // 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";
3864
+ 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
3865
  import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3821
3866
  var DialogoDeColumnas = ({ open, onClose, columnas, ocultas, alternarColumna }) => {
3822
3867
  const lista = (titulo, nombres, idDeLista) => /* @__PURE__ */ jsxs16(Box11, { sx: { flex: 1, minWidth: 0 }, children: [
@@ -3829,7 +3874,7 @@ var DialogoDeColumnas = ({ open, onClose, columnas, ocultas, alternarColumna })
3829
3874
  lista("Columnas mostradas", columnas.filter((nombre) => !ocultas.has(nombre)), "columnas-mostradas"),
3830
3875
  lista("Columnas ocultas", columnas.filter((nombre) => ocultas.has(nombre)), "columnas-ocultas")
3831
3876
  ] }) }),
3832
- /* @__PURE__ */ jsx21(DialogActions4, { children: /* @__PURE__ */ jsx21(Button7, { onClick: onClose, children: "Cerrar" }) })
3877
+ /* @__PURE__ */ jsx21(DialogActions4, { children: /* @__PURE__ */ jsx21(Button6, { onClick: onClose, children: "Cerrar" }) })
3833
3878
  ] });
3834
3879
  };
3835
3880
 
@@ -3839,11 +3884,11 @@ import {
3839
3884
  DialogTitle as DialogTitle5,
3840
3885
  DialogContent as DialogContent5,
3841
3886
  DialogActions as DialogActions5,
3842
- Button as Button8,
3887
+ Button as Button7,
3843
3888
  Alert,
3844
3889
  Box as Box12,
3845
3890
  CircularProgress as CircularProgress3,
3846
- IconButton as IconButton5,
3891
+ IconButton as IconButton6,
3847
3892
  Typography as Typography10
3848
3893
  } from "@mui/material";
3849
3894
  import CloseIcon from "@mui/icons-material/Close";
@@ -3983,7 +4028,7 @@ var VerticalEditorPage = ({
3983
4028
  " - ",
3984
4029
  tableDefinition.title || tableName
3985
4030
  ] }),
3986
- /* @__PURE__ */ jsx22(IconButton5, { onClick: onClose, size: "small", edge: "end", children: /* @__PURE__ */ jsx22(CloseIcon, {}) })
4031
+ /* @__PURE__ */ jsx22(IconButton6, { onClick: onClose, size: "small", edge: "end", children: /* @__PURE__ */ jsx22(CloseIcon, {}) })
3987
4032
  ] }),
3988
4033
  /* @__PURE__ */ jsxs17(DialogContent5, { dividers: true, sx: { px: 2, flexGrow: 1, py: 1 }, children: [
3989
4034
  globalError && /* @__PURE__ */ jsx22(Alert, { severity: "error", sx: { mb: 2 }, children: globalError }),
@@ -4009,7 +4054,7 @@ var VerticalEditorPage = ({
4009
4054
  isSaving && /* @__PURE__ */ jsx22(CircularProgress3, { size: 20, sx: { ml: 2 } }),
4010
4055
  /* @__PURE__ */ jsx22("span", { style: { marginLeft: 10, fontSize: "0.8rem", color: "#666" }, children: isSaving ? "Guardando..." : "Cambios auto-guardados" })
4011
4056
  ] }),
4012
- /* @__PURE__ */ jsx22(Button8, { variant: "outlined", onClick: onClose, children: "Cerrar" })
4057
+ /* @__PURE__ */ jsx22(Button7, { variant: "outlined", onClick: onClose, children: "Cerrar" })
4013
4058
  ] })
4014
4059
  ] });
4015
4060
  };
@@ -4097,6 +4142,7 @@ var ALTO_TITULO_ANIDADA = 26;
4097
4142
  var ALTO_MINIMO_CUERPO_ANIDADA = 120;
4098
4143
  var HOLGURA_ANIDADA = 24;
4099
4144
  var ANCHO_MINIMO_ANIDADA = 240;
4145
+ var ANCHO_RESERVADO_PANTALLA = 320;
4100
4146
  var SANGRIA_DETALLE = 28 + 8 + 3;
4101
4147
  var MARGEN_INFERIOR_PAGINA = 24;
4102
4148
  var PRIMERAS_FILAS_ANIDADA = 20;
@@ -4312,7 +4358,7 @@ var GenericDataGrid = ({
4312
4358
  const firstDisplayCount = tableDefinition?.firstDisplayCount ?? PRIMERAS_FILAS_ANIDADA;
4313
4359
  const firstDisplayOverLimit = tableDefinition?.firstDisplayOverLimit ?? LIMITE_PRIMERAS_FILAS_ANIDADA;
4314
4360
  const filasVisiblesMax = anidada ? filteredRows.length <= firstDisplayOverLimit ? filteredRows.length : firstDisplayCount : void 0;
4315
- const conPie = !anidada || !!tableDefinition?.fields.some((field) => field.aggregate);
4361
+ const conPie = !!tableDefinition?.fields.some((field) => field.aggregate);
4316
4362
  const { gridHeight, bottomSummaryRow, summaryRows, filterHeight } = useGridLayout({
4317
4363
  filteredRows,
4318
4364
  isFilterRowVisible,
@@ -4406,7 +4452,7 @@ var GenericDataGrid = ({
4406
4452
  const definition = parametros.tableDef ? fusionar(definicionDelServidor, parametros.tableDef) : definicionDelServidor;
4407
4453
  setTableDefinition(definition);
4408
4454
  setSortColumns(getInitialSortColumns(definition));
4409
- setAnchosCalculados(calcularAnchosDeColumnas(definition, data));
4455
+ setAnchosCalculados(calcularAnchosDeColumnas(definition, data, window.innerWidth - ANCHO_RESERVADO_PANTALLA));
4410
4456
  setColumnasOcultas(columnasOcultasIniciales(definition));
4411
4457
  setTableData(data);
4412
4458
  const filasDeFiltros = parametros.filterColumns ?? aFiltrosIniciales(definition.filterColumns);
@@ -4607,7 +4653,7 @@ var GenericDataGrid = ({
4607
4653
  [exitingRowIds, primaryKey]
4608
4654
  );
4609
4655
  const anchoContenido = useMemo10(() => {
4610
- if (!anidada || columns.length === 0) return void 0;
4656
+ if (columns.length === 0) return void 0;
4611
4657
  const sumaColumnas = columns.reduce((total, col) => {
4612
4658
  const medido = columnWidths.get(col.key)?.width;
4613
4659
  return total + (typeof medido === "number" ? medido : typeof col.width === "number" ? col.width : 100);
@@ -4617,8 +4663,9 @@ var GenericDataGrid = ({
4617
4663
  return Math.max(ANCHO_MINIMO_ANIDADA, propio, anchoDetalleMax > 0 ? anchoDetalleMax + SANGRIA_DETALLE + (medidas?.barra ?? 0) + 2 : 0);
4618
4664
  }, [anidada, columns, columnWidths, medidas?.barra, detallesAbiertos, anchosDetalle]);
4619
4665
  useEffect6(() => {
4620
- if (anchoContenido !== void 0) onAnchoDeseado?.(anchoContenido);
4621
- }, [onAnchoDeseado, anchoContenido]);
4666
+ if (anidada && anchoContenido !== void 0) onAnchoDeseado?.(anchoContenido);
4667
+ }, [onAnchoDeseado, anidada, anchoContenido]);
4668
+ const anchoExterior = anchoContenido === void 0 ? void 0 : anidada ? anchoContenido : anchoContenido - 2 + 32 + ancestors.length * 32;
4622
4669
  const estiloGrilla = useMemo10(() => ({ height: "100%", width: "100%", boxSizing: "border-box", ...gridStyles }), [gridStyles]);
4623
4670
  const bottomSummaryRows = useMemo10(() => conPie ? [bottomSummaryRow] : [], [conPie, bottomSummaryRow]);
4624
4671
  const vistaGrilla = useMemo10(() => ({ isFilterRowVisible }), [isFilterRowVisible]);
@@ -4644,7 +4691,7 @@ var GenericDataGrid = ({
4644
4691
  if (!tableDefinition) {
4645
4692
  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
4693
  }
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: [
4694
+ 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
4695
  !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
4696
  StaticAncestorGrid,
4650
4697
  {
@@ -4741,7 +4788,7 @@ var GenericDataGrid = ({
4741
4788
  sx: {
4742
4789
  position: "absolute",
4743
4790
  top: filterHeight + HEADER_ROW_HEIGHT + 1,
4744
- bottom: anidada ? (conPie ? SUMMARY_ROW_HEIGHT : 0) + 4 : 102,
4791
+ bottom: anidada ? (conPie ? SUMMARY_ROW_HEIGHT : 0) + 4 : conPie ? 102 : 102 - SUMMARY_ROW_HEIGHT,
4745
4792
  left: 18,
4746
4793
  right: 0,
4747
4794
  display: "flex",
@@ -4866,8 +4913,8 @@ var GenericDataGrid = ({
4866
4913
  var GenericDataGrid_default = GenericDataGrid;
4867
4914
 
4868
4915
  // 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";
4916
+ import React34, { useEffect as useEffect18, useMemo as useMemo17 } from "react";
4917
+ import { BrowserRouter, Routes, Route, useLocation as useLocation9, Navigate as Navigate4, Outlet as Outlet2 } from "react-router-dom";
4871
4918
 
4872
4919
  // src/components/MainLayout.tsx
4873
4920
  import { useEffect as useEffect9 } from "react";
@@ -4877,10 +4924,10 @@ import {
4877
4924
  AppBar,
4878
4925
  Toolbar,
4879
4926
  Typography as Typography13,
4880
- Button as Button9,
4927
+ Button as Button8,
4881
4928
  Drawer,
4882
4929
  CssBaseline,
4883
- IconButton as IconButton6,
4930
+ IconButton as IconButton8,
4884
4931
  useTheme as useTheme5,
4885
4932
  useMediaQuery
4886
4933
  } from "@mui/material";
@@ -4889,6 +4936,15 @@ import {
4889
4936
  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
4937
  import { useLocation, useNavigate } from "react-router-dom";
4891
4938
  import { useSelector as useSelector2, useDispatch as useDispatch2 } from "react-redux";
4939
+
4940
+ // src/utils/cierreVoluntario.ts
4941
+ var cierreVoluntario = false;
4942
+ var marcarCierreVoluntario = (activo) => {
4943
+ cierreVoluntario = activo;
4944
+ };
4945
+ var huboCierreVoluntario = () => cierreVoluntario;
4946
+
4947
+ // src/contexts/AppContext.tsx
4892
4948
  import { jsx as jsx24 } from "react/jsx-runtime";
4893
4949
  var AuthContext = createContext4(void 0);
4894
4950
  var useApp = () => {
@@ -4905,9 +4961,10 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4905
4961
  const [showSessionExpiredMessage, setShowSessionExpiredMessage] = useState17(false);
4906
4962
  const isRedirectingRef = useRef8(false);
4907
4963
  const huboSesionRef = useRef8(false);
4964
+ const cierreVoluntarioRef = useRef8(false);
4908
4965
  const dispatch = useDispatch2();
4909
4966
  const clientContext = useSelector2((state) => state.clientContext);
4910
- if (clientContext.status === "succeeded") huboSesionRef.current = true;
4967
+ if (clientContext.status === "succeeded" && !cierreVoluntarioRef.current) huboSesionRef.current = true;
4911
4968
  const navigate = useNavigate();
4912
4969
  const handleExpiredSessionRedirect = useCallback13(() => {
4913
4970
  isRedirectingRef.current = true;
@@ -4918,6 +4975,17 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4918
4975
  isRedirectingRef.current = false;
4919
4976
  }, 2e3);
4920
4977
  }, [navigate]);
4978
+ const avisarCierreVoluntario = useCallback13((activo) => {
4979
+ cierreVoluntarioRef.current = activo;
4980
+ marcarCierreVoluntario(activo);
4981
+ if (activo) huboSesionRef.current = false;
4982
+ }, []);
4983
+ const limpiarContextoPorSesionPerdida = useCallback13(() => {
4984
+ if (clientContext.status === "succeeded" && clientContext.username !== "" && !cierreVoluntarioRef.current) {
4985
+ dispatch(setRedirectUsername(clientContext.username));
4986
+ }
4987
+ dispatch(clearClientContext());
4988
+ }, [dispatch, clientContext.status, clientContext.username]);
4921
4989
  const location = useLocation();
4922
4990
  const isCurrentPathPublic = useCallback13(() => {
4923
4991
  const current = location.pathname;
@@ -4943,8 +5011,8 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4943
5011
  }
4944
5012
  } else {
4945
5013
  setIsLoggedIn(false);
4946
- dispatch(clearClientContext());
4947
- if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current) {
5014
+ limpiarContextoPorSesionPerdida();
5015
+ if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current && !cierreVoluntarioRef.current) {
4948
5016
  console.warn("Sesi\xF3n inv\xE1lida en ruta protegida. Mostrando modal.");
4949
5017
  setShowSessionExpiredMessage(true);
4950
5018
  } else {
@@ -4954,13 +5022,13 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4954
5022
  } catch (error) {
4955
5023
  console.error("Error checking session:", error);
4956
5024
  setIsLoggedIn(false);
4957
- dispatch(clearClientContext());
5025
+ limpiarContextoPorSesionPerdida();
4958
5026
  const isPublic = publicPaths.some((p) => location.pathname === p || location.pathname.startsWith(p + "/"));
4959
- if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current) {
5027
+ if (!showSessionExpiredMessage && !isCurrentPathPublic() && huboSesionRef.current && !cierreVoluntarioRef.current) {
4960
5028
  setShowSessionExpiredMessage(true);
4961
5029
  }
4962
5030
  }
4963
- }, [dispatch, clientContext.status, showSessionExpiredMessage, isCurrentPathPublic]);
5031
+ }, [dispatch, clientContext.status, showSessionExpiredMessage, isCurrentPathPublic, limpiarContextoPorSesionPerdida]);
4964
5032
  useEffect7(() => {
4965
5033
  checkSession();
4966
5034
  const intervalId = setInterval(checkSession, 15 * 1e3);
@@ -4974,26 +5042,30 @@ var AppProvider = ({ children, publicPaths = [] }) => {
4974
5042
  checkSession,
4975
5043
  showSessionExpiredMessage,
4976
5044
  setShowSessionExpiredMessage,
4977
- handleExpiredSessionRedirect
5045
+ handleExpiredSessionRedirect,
5046
+ avisarCierreVoluntario
4978
5047
  }), [
4979
5048
  isLoggedIn,
4980
5049
  setIsLoggedIn,
4981
5050
  checkSession,
4982
5051
  showSessionExpiredMessage,
4983
5052
  setShowSessionExpiredMessage,
4984
- handleExpiredSessionRedirect
5053
+ handleExpiredSessionRedirect,
5054
+ avisarCierreVoluntario
4985
5055
  ]);
4986
5056
  return /* @__PURE__ */ jsx24(AuthContext.Provider, { value: authContextValue, children });
4987
5057
  };
4988
5058
 
4989
5059
  // src/components/MainLayout.tsx
5060
+ import { useNavigate as useNavigate4 } from "react-router-dom";
4990
5061
  import LogoutIcon from "@mui/icons-material/Logout";
5062
+ import VpnKeyIcon from "@mui/icons-material/VpnKey";
4991
5063
  import MenuIcon from "@mui/icons-material/Menu";
4992
5064
  import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
4993
5065
 
4994
5066
  // 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";
5067
+ import React19, { useEffect as useEffect8, useMemo as useMemo12, useRef as useRef9, useState as useState18 } from "react";
5068
+ 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
5069
  import ExpandLess from "@mui/icons-material/ExpandLess";
4998
5070
  import ExpandMore from "@mui/icons-material/ExpandMore";
4999
5071
  import TableChartIcon from "@mui/icons-material/TableChart";
@@ -5001,10 +5073,17 @@ import FolderIcon from "@mui/icons-material/Folder";
5001
5073
  import DnsIcon from "@mui/icons-material/Dns";
5002
5074
  import WarningAmberIcon3 from "@mui/icons-material/WarningAmber";
5003
5075
  import DesktopWindowsIcon from "@mui/icons-material/DesktopWindows";
5004
- import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
5005
- import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
5076
+ import SearchIcon3 from "@mui/icons-material/Search";
5077
+ import ClearIcon from "@mui/icons-material/Clear";
5006
5078
  import { useNavigate as useNavigate2, useLocation as useLocation2 } from "react-router-dom";
5007
- import { blue, orange, teal, red, grey } from "@mui/material/colors";
5079
+ import { blue, orange, teal, indigo, red } from "@mui/material/colors";
5080
+
5081
+ // src/puente/habilitacion.ts
5082
+ var habilitado = false;
5083
+ var habilitarPuenteBP = () => {
5084
+ habilitado = true;
5085
+ };
5086
+ var puenteBPHabilitado = () => habilitado;
5008
5087
 
5009
5088
  // src/utils/menuInicial.ts
5010
5089
  function rutaDeTablaDeMenu(camino) {
@@ -5056,16 +5135,93 @@ function itemInicial(menu) {
5056
5135
  return elegido.menuType === "menu" ? itemInicial(elegido.menuContent) : elegido;
5057
5136
  }
5058
5137
 
5138
+ // src/utils/busquedaDeMenu.ts
5139
+ function etiquetaDeItem(item) {
5140
+ return item.label || item.name;
5141
+ }
5142
+ function claveDeMenu(camino) {
5143
+ return camino.join("/");
5144
+ }
5145
+ function hojasDeMenu(menu, grupos = [], etiquetasDeGrupos = []) {
5146
+ return menu.flatMap(
5147
+ (item) => item.menuType === "menu" ? hojasDeMenu(item.menuContent, [...grupos, item.name], [...etiquetasDeGrupos, etiquetaDeItem(item)]) : [{ item, grupos, etiquetasDeGrupos, clave: claveDeMenu([...grupos, item.name]) }]
5148
+ );
5149
+ }
5150
+ function normalizarParaBuscar(texto) {
5151
+ return texto.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/_/g, " ").toLowerCase();
5152
+ }
5153
+ function buscarEnMenu(hojas, consulta) {
5154
+ const palabras = normalizarParaBuscar(consulta).split(/\s+/).filter((palabra) => palabra !== "");
5155
+ if (palabras.length === 0) return [];
5156
+ const propias = [];
5157
+ const porGrupo = [];
5158
+ for (const hoja of hojas) {
5159
+ const propia = normalizarParaBuscar(`${etiquetaDeItem(hoja.item)} ${hoja.item.name}`);
5160
+ const completa = `${hoja.etiquetasDeGrupos.map(normalizarParaBuscar).join(" ")} ${propia}`;
5161
+ if (!palabras.every((palabra) => completa.includes(palabra))) continue;
5162
+ (palabras.every((palabra) => propia.includes(palabra)) ? propias : porGrupo).push(hoja);
5163
+ }
5164
+ return [...propias, ...porGrupo];
5165
+ }
5166
+
5059
5167
  // src/components/SideMenu.tsx
5060
- import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
5061
- var MenuListItem = ({ item, level, grupos = [], onMenuItemClick }) => {
5168
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
5169
+ var CLAVE_RECIENTES = "fpr.menu.recientes";
5170
+ var MAXIMO_RECIENTES = 4;
5171
+ var getIcon = (item, destacado = false) => {
5172
+ switch (item.menuType) {
5173
+ case "table":
5174
+ return /* @__PURE__ */ jsx25(TableChartIcon, { sx: destacado ? { color: blue[700] } : void 0 });
5175
+ case "proc":
5176
+ return /* @__PURE__ */ jsx25(DnsIcon, { sx: destacado ? { color: teal[700] } : void 0 });
5177
+ case "menu":
5178
+ return /* @__PURE__ */ jsx25(FolderIcon, { sx: destacado ? { color: orange[800] } : void 0 });
5179
+ default:
5180
+ if (wScreens[item.menuType] !== void 0 || puenteBPHabilitado()) {
5181
+ return /* @__PURE__ */ jsx25(DesktopWindowsIcon, { sx: destacado ? { color: indigo[500] } : void 0 });
5182
+ }
5183
+ return /* @__PURE__ */ jsx25(WarningAmberIcon3, { sx: destacado ? { color: red[500] } : void 0 });
5184
+ }
5185
+ };
5186
+ var estaSeleccionado = (item, grupos, currentPath, hash = "") => {
5187
+ const pathParts = currentPath.split("/");
5188
+ const urlTableName = pathParts[1] === "table" ? pathParts[2] : null;
5189
+ if (item.menuType === "table") {
5190
+ return currentPath === rutaDeTablaDeMenu([...grupos, item.name]) || urlTableName === (item.table || item.name);
5191
+ } else if (item.menuType === "proc") {
5192
+ return currentPath === `/procedures/${item.name}`;
5193
+ } else if (item.menuType !== "menu") {
5194
+ return currentPath.startsWith(`/wScreens/${item.menuType}`) || currentPath.startsWith(`/wScreens-fallback/${item.menuType}`) || currentPath === "/bp/menu" && new URLSearchParams(hash.replace(/^[#?]/, "")).get("w") === item.menuType;
5195
+ }
5196
+ return false;
5197
+ };
5198
+ var leerRecientes = () => {
5199
+ try {
5200
+ const guardado = JSON.parse(localStorage.getItem(CLAVE_RECIENTES) ?? "[]");
5201
+ return Array.isArray(guardado) ? guardado.filter((clave) => typeof clave === "string") : [];
5202
+ } catch {
5203
+ return [];
5204
+ }
5205
+ };
5206
+ var guardarRecientes = (recientes) => {
5207
+ try {
5208
+ localStorage.setItem(CLAVE_RECIENTES, JSON.stringify(recientes));
5209
+ } catch {
5210
+ }
5211
+ };
5212
+ var MenuListItem = ({ item, level, grupos = [], hermanos = [], onMenuItemClick }) => {
5062
5213
  const navigate = useNavigate2();
5063
5214
  const location = useLocation2();
5064
- const open = useSubMenuOpenState(item.name);
5215
+ const clave = claveDeMenu([...grupos, item.name]);
5216
+ const open = useSubMenuOpenState(clave);
5065
5217
  const dispatch = useAppDispatch();
5066
5218
  const handleClick = () => {
5067
5219
  if (item.menuType === "menu") {
5068
- dispatch(toggleSubMenu(item.name));
5220
+ if (open) {
5221
+ dispatch(setSubMenuOpen({ menuName: clave, isOpen: false }));
5222
+ } else {
5223
+ dispatch(abrirGrupo({ clave, hermanos }));
5224
+ }
5069
5225
  } else {
5070
5226
  navigate(rutaDeItemDeMenu(item, grupos));
5071
5227
  if (onMenuItemClick) {
@@ -5073,39 +5229,14 @@ var MenuListItem = ({ item, level, grupos = [], onMenuItemClick }) => {
5073
5229
  }
5074
5230
  }
5075
5231
  };
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
- }
5232
+ const isSelected = estaSeleccionado(item, grupos, location.pathname, location.hash);
5103
5233
  const itemRef = React19.useRef(null);
5104
5234
  useEffect8(() => {
5105
5235
  if (isSelected && itemRef.current) {
5106
5236
  itemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
5107
5237
  }
5108
5238
  }, [isSelected]);
5239
+ const contenido = item.menuType === "menu" ? item.menuContent : [];
5109
5240
  return /* @__PURE__ */ jsxs19("div", { ref: itemRef, children: [
5110
5241
  /* @__PURE__ */ jsxs19(
5111
5242
  ListItemButton4,
@@ -5114,83 +5245,156 @@ var MenuListItem = ({ item, level, grupos = [], onMenuItemClick }) => {
5114
5245
  sx: { pl: level * 2 },
5115
5246
  selected: isSelected,
5116
5247
  children: [
5117
- /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(item) }),
5118
- /* @__PURE__ */ jsx25(ListItemText3, { primary: item.label || item.name }),
5248
+ /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(item, isSelected || open) }),
5249
+ /* @__PURE__ */ jsx25(ListItemText3, { primary: etiquetaDeItem(item) }),
5119
5250
  item.menuType === "menu" ? open ? /* @__PURE__ */ jsx25(ExpandLess, {}) : /* @__PURE__ */ jsx25(ExpandMore, {}) : null
5120
5251
  ]
5121
5252
  }
5122
5253
  ),
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(
5254
+ 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
5255
  MenuListItem,
5125
5256
  {
5126
5257
  item: subItem,
5127
5258
  level: level + 1,
5128
5259
  grupos: [...grupos, item.name],
5260
+ hermanos: contenido.filter((otro) => otro !== subItem).map((otro) => claveDeMenu([...grupos, item.name, otro.name])),
5129
5261
  onMenuItemClick
5130
5262
  },
5131
5263
  subItem.name
5132
5264
  )) }) })
5133
5265
  ] });
5134
5266
  };
5267
+ var ItemPlano = ({ hoja, seleccionado, traerALaVista = false, onElegir }) => {
5268
+ const boton = useRef9(null);
5269
+ useEffect8(() => {
5270
+ if (traerALaVista && seleccionado) boton.current?.scrollIntoView({ block: "nearest" });
5271
+ }, [traerALaVista, seleccionado]);
5272
+ return /* @__PURE__ */ jsxs19(ListItemButton4, { dense: true, selected: seleccionado, onClick: () => onElegir(hoja), ref: boton, children: [
5273
+ /* @__PURE__ */ jsx25(ListItemIcon2, { sx: { minWidth: "38px" }, children: getIcon(hoja.item, seleccionado) }),
5274
+ /* @__PURE__ */ jsx25(
5275
+ ListItemText3,
5276
+ {
5277
+ primary: etiquetaDeItem(hoja.item),
5278
+ secondary: hoja.etiquetasDeGrupos.join(" \u203A ") || void 0,
5279
+ secondaryTypographyProps: { noWrap: true }
5280
+ }
5281
+ )
5282
+ ] });
5283
+ };
5135
5284
  var SideMenu = ({ onMenuItemClick }) => {
5136
5285
  const { clientContext } = useApp();
5137
5286
  const navigate = useNavigate2();
5138
5287
  const location = useLocation2();
5139
5288
  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) {
5289
+ const [consulta, setConsulta] = useState18("");
5290
+ const [indice, setIndice] = useState18(0);
5291
+ const [recientes, setRecientes] = useState18(leerRecientes);
5292
+ const campoDeBusqueda = useRef9(null);
5293
+ const menu = clientContext?.menu;
5294
+ const hojas = useMemo12(() => hojasDeMenu(menu ?? []), [menu]);
5295
+ const resultados = useMemo12(() => buscarEnMenu(hojas, consulta), [hojas, consulta]);
5296
+ useEffect8(() => {
5297
+ const alApretar = (evento) => {
5298
+ if ((evento.ctrlKey || evento.metaKey) && !evento.altKey && !evento.shiftKey && evento.key.toLowerCase() === "k") {
5299
+ evento.preventDefault();
5300
+ dispatch(setDrawerOpen(true));
5301
+ setTimeout(() => {
5302
+ campoDeBusqueda.current?.focus();
5303
+ campoDeBusqueda.current?.select();
5304
+ }, 50);
5305
+ }
5306
+ };
5307
+ window.addEventListener("keydown", alApretar);
5308
+ return () => window.removeEventListener("keydown", alApretar);
5309
+ }, [dispatch]);
5310
+ useEffect8(() => {
5311
+ const actual = hojas.find((hoja) => estaSeleccionado(hoja.item, hoja.grupos, location.pathname, location.hash));
5312
+ if (!actual) return;
5313
+ dispatch(abrirCamino(actual.grupos.map((_, i) => claveDeMenu(actual.grupos.slice(0, i + 1)))));
5314
+ setRecientes((anteriores) => {
5315
+ if (anteriores.includes(actual.clave)) return anteriores;
5316
+ const nuevos = [actual.clave, ...anteriores].slice(0, MAXIMO_RECIENTES);
5317
+ guardarRecientes(nuevos);
5318
+ return nuevos;
5319
+ });
5320
+ }, [location.pathname, location.hash, hojas, dispatch]);
5321
+ if (!menu) {
5148
5322
  return /* @__PURE__ */ jsx25(Box14, { sx: { p: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx25(Typography12, { variant: "body2", color: "text.secondary", children: "Cargando men\xFA..." }) });
5149
5323
  }
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;
5324
+ const elegir = (hoja) => {
5325
+ navigate(rutaDeItemDeMenu(hoja.item, hoja.grupos));
5326
+ setConsulta("");
5327
+ setIndice(0);
5328
+ if (onMenuItemClick) {
5329
+ onMenuItemClick();
5330
+ }
5331
+ };
5332
+ const alApretarEnBusqueda = (evento) => {
5333
+ if (evento.key === "ArrowDown" || evento.key === "ArrowUp") {
5334
+ evento.preventDefault();
5335
+ if (resultados.length === 0) return;
5336
+ setIndice((actual) => (actual + (evento.key === "ArrowDown" ? 1 : -1) + resultados.length) % resultados.length);
5337
+ } else if (evento.key === "Enter") {
5338
+ evento.preventDefault();
5339
+ const hoja = resultados[indice];
5340
+ if (hoja) elegir(hoja);
5341
+ } else if (evento.key === "Escape" && consulta !== "") {
5342
+ evento.stopPropagation();
5343
+ setConsulta("");
5344
+ }
5345
+ };
5346
+ const hojasRecientes = recientes.map((clave) => hojas.find((hoja) => hoja.clave === clave)).filter((hoja) => hoja !== void 0);
5347
+ return /* @__PURE__ */ jsxs19(Box14, { sx: { width: "100%", flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
5348
+ /* @__PURE__ */ jsx25(Box14, { sx: { px: 1.5, py: 1, flexShrink: 0 }, children: /* @__PURE__ */ jsx25(
5349
+ TextField2,
5350
+ {
5351
+ inputRef: campoDeBusqueda,
5352
+ value: consulta,
5353
+ onChange: (evento) => {
5354
+ setConsulta(evento.target.value);
5355
+ setIndice(0);
5356
+ },
5357
+ onKeyDown: alApretarEnBusqueda,
5358
+ placeholder: "Buscar (Ctrl+K)",
5359
+ size: "small",
5360
+ fullWidth: true,
5361
+ autoComplete: "off",
5362
+ sx: { "& .MuiInputBase-input": { fontSize: "0.8rem" } },
5363
+ InputProps: {
5364
+ startAdornment: /* @__PURE__ */ jsx25(InputAdornment2, { position: "start", children: /* @__PURE__ */ jsx25(SearchIcon3, { fontSize: "small" }) }),
5365
+ endAdornment: consulta !== "" ? /* @__PURE__ */ jsx25(InputAdornment2, { position: "end", children: /* @__PURE__ */ jsx25(IconButton7, { size: "small", "aria-label": "borrar b\xFAsqueda", edge: "end", onClick: () => {
5366
+ setConsulta("");
5367
+ campoDeBusqueda.current?.focus();
5368
+ }, children: /* @__PURE__ */ jsx25(ClearIcon, { fontSize: "small" }) }) }) : void 0
5173
5369
  }
5174
5370
  }
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
- ] }) }),
5371
+ ) }),
5184
5372
  /* @__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
- )) })
5373
+ /* @__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: [
5374
+ hojasRecientes.length > 0 && /* @__PURE__ */ jsxs19(Fragment5, { children: [
5375
+ /* @__PURE__ */ jsx25(Typography12, { variant: "overline", color: "text.secondary", sx: { display: "block", px: 2, pt: 1, lineHeight: 2 }, children: "Recientes" }),
5376
+ /* @__PURE__ */ jsx25(List4, { disablePadding: true, children: hojasRecientes.map((hoja) => /* @__PURE__ */ jsx25(
5377
+ ItemPlano,
5378
+ {
5379
+ hoja,
5380
+ seleccionado: estaSeleccionado(hoja.item, hoja.grupos, location.pathname, location.hash),
5381
+ onElegir: elegir
5382
+ },
5383
+ hoja.clave
5384
+ )) }),
5385
+ /* @__PURE__ */ jsx25(Divider2, {})
5386
+ ] }),
5387
+ /* @__PURE__ */ jsx25(List4, { children: menu.map((menuItem) => /* @__PURE__ */ jsx25(
5388
+ MenuListItem,
5389
+ {
5390
+ item: menuItem,
5391
+ level: 1,
5392
+ hermanos: menu.filter((otro) => otro !== menuItem).map((otro) => claveDeMenu([otro.name])),
5393
+ onMenuItemClick
5394
+ },
5395
+ menuItem.name
5396
+ )) })
5397
+ ] }) })
5194
5398
  ] });
5195
5399
  };
5196
5400
  var SideMenu_default = SideMenu;
@@ -5200,10 +5404,11 @@ import { useCallback as useCallback14 } from "react";
5200
5404
  import { useNavigate as useNavigate3 } from "react-router-dom";
5201
5405
  import { useDispatch as useDispatch3 } from "react-redux";
5202
5406
  var useLogout = () => {
5203
- const { setIsLoggedIn } = useApp();
5407
+ const { setIsLoggedIn, avisarCierreVoluntario } = useApp();
5204
5408
  const dispatch = useDispatch3();
5205
5409
  const navigate = useNavigate3();
5206
5410
  const logout = useCallback14(async () => {
5411
+ avisarCierreVoluntario(true);
5207
5412
  try {
5208
5413
  const response = await fetchApi("/logout", { method: "GET" });
5209
5414
  if (response.status === 401 || response.ok) {
@@ -5216,9 +5421,10 @@ var useLogout = () => {
5216
5421
  } finally {
5217
5422
  setIsLoggedIn(false);
5218
5423
  dispatch(clearClientContext());
5424
+ dispatch(setRedirectPath(null));
5219
5425
  navigate("/login", { replace: true });
5220
5426
  }
5221
- }, [setIsLoggedIn, dispatch, navigate]);
5427
+ }, [setIsLoggedIn, avisarCierreVoluntario, dispatch, navigate]);
5222
5428
  return logout;
5223
5429
  };
5224
5430
  var useLogout_default = useLogout;
@@ -5229,6 +5435,7 @@ var drawerWidth = 240;
5229
5435
  var MainLayout = () => {
5230
5436
  const { clientContext } = useApp();
5231
5437
  const logout = useLogout_default();
5438
+ const navigate = useNavigate4();
5232
5439
  const isDrawerOpen = useIsDrawerOpen();
5233
5440
  const dispatch = useAppDispatch();
5234
5441
  const theme = useTheme5();
@@ -5272,7 +5479,7 @@ var MainLayout = () => {
5272
5479
  },
5273
5480
  children: /* @__PURE__ */ jsxs20(Toolbar, { children: [
5274
5481
  /* @__PURE__ */ jsx26(
5275
- IconButton6,
5482
+ IconButton8,
5276
5483
  {
5277
5484
  color: "inherit",
5278
5485
  "aria-label": "open drawer",
@@ -5294,8 +5501,9 @@ var MainLayout = () => {
5294
5501
  ),
5295
5502
  /* @__PURE__ */ jsx26(Typography13, { variant: "h6", noWrap: true, component: "div", sx: { flexGrow: 1 }, children: isDrawerOpen ? "" : clientContext.config.title }),
5296
5503
  /* @__PURE__ */ jsx26(Typography13, { variant: "subtitle1", component: "div", sx: { mr: 2 }, children: clientContext.username }),
5504
+ /* @__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
5505
  /* @__PURE__ */ jsx26(
5298
- Button9,
5506
+ Button8,
5299
5507
  {
5300
5508
  color: "inherit",
5301
5509
  onClick: logout,
@@ -5318,8 +5526,9 @@ var MainLayout = () => {
5318
5526
  "& .MuiDrawer-paper": {
5319
5527
  width: drawerWidth,
5320
5528
  boxSizing: "border-box",
5321
- overflowX: "hidden",
5322
- overflowY: "auto"
5529
+ overflow: "hidden",
5530
+ display: "flex",
5531
+ flexDirection: "column"
5323
5532
  }
5324
5533
  },
5325
5534
  children: [
@@ -5331,6 +5540,8 @@ var MainLayout = () => {
5331
5540
  alignItems: "center",
5332
5541
  justifyContent: "flex-start",
5333
5542
  px: 1,
5543
+ flexShrink: 0,
5544
+ // el logo queda fijo: solo scrollea la lista del menú
5334
5545
  fontSize: "1.3rem",
5335
5546
  minHeight: theme.mixins.toolbar.minHeight,
5336
5547
  gap: 1.5
@@ -5389,7 +5600,7 @@ var MainLayout = () => {
5389
5600
  var MainLayout_default = MainLayout;
5390
5601
 
5391
5602
  // src/components/SessionExpiredMessage.tsx
5392
- import { Box as Box16, Typography as Typography14, Button as Button10, Modal } from "@mui/material";
5603
+ import { Box as Box16, Typography as Typography14, Button as Button9, Modal } from "@mui/material";
5393
5604
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
5394
5605
  var style = {
5395
5606
  position: "absolute",
@@ -5432,7 +5643,7 @@ var SessionExpiredMessage = () => {
5432
5643
  children: /* @__PURE__ */ jsxs21(Box16, { sx: style, children: [
5433
5644
  /* @__PURE__ */ jsx27(Typography14, { id: "session-expired-modal-title", variant: "h6", component: "h2", gutterBottom: true, children: "\xA1Sesi\xF3n Expirada!" }),
5434
5645
  /* @__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" })
5646
+ /* @__PURE__ */ jsx27(Button9, { variant: "contained", color: "primary", onClick: handleClose, children: "Ir al Login" })
5436
5647
  ] })
5437
5648
  }
5438
5649
  );
@@ -5473,13 +5684,24 @@ var LogoutPage = () => {
5473
5684
  var LogoutPage_default = LogoutPage;
5474
5685
 
5475
5686
  // 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";
5687
+ import { useState as useState19 } from "react";
5688
+ import { Button as Button10, TextField as TextField3, Typography as Typography16, Alert as Alert3, AlertTitle, Box as Box18, CircularProgress as CircularProgress6, Link } from "@mui/material";
5689
+ import { useLocation as useLocation3 } from "react-router-dom";
5690
+
5691
+ // src/pages/configuracionLogin.ts
5692
+ var RUTA_POR_OMISION = "/new-pass";
5693
+ var rutaDeRecuperarClave = null;
5694
+ var configurarRecuperarClave = (recuperarClave) => {
5695
+ rutaDeRecuperarClave = recuperarClave === true ? RUTA_POR_OMISION : typeof recuperarClave === "string" && recuperarClave !== "" ? recuperarClave : null;
5696
+ };
5697
+ var rutaParaRecuperarClave = () => rutaDeRecuperarClave;
5698
+
5699
+ // src/pages/LoginPage.tsx
5478
5700
  import { useForm, Controller } from "react-hook-form";
5479
5701
  import { zodResolver } from "@hookform/resolvers/zod";
5480
5702
  import * as z from "zod";
5481
5703
  import { useDispatch as useDispatch4 } from "react-redux";
5482
- import { Fragment as Fragment5, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
5704
+ import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
5483
5705
  var loginSchema = z.object({
5484
5706
  username: z.string().min(1, "El usuario es requerido"),
5485
5707
  // Añadir validación básica
@@ -5487,9 +5709,12 @@ var loginSchema = z.object({
5487
5709
  // Añadir validación básica
5488
5710
  });
5489
5711
  var LoginPage = () => {
5490
- const [loginError, setLoginError] = useState18(null);
5491
- const [isSubmitting, setIsSubmitting] = useState18(false);
5492
- const { setIsLoggedIn, checkSession } = useApp();
5712
+ const [loginError, setLoginError] = useState19(null);
5713
+ const [isSubmitting, setIsSubmitting] = useState19(false);
5714
+ const estadoDeLaRuta = useLocation3().state;
5715
+ const claveCambiada = typeof estadoDeLaRuta === "object" && estadoDeLaRuta !== null && "claveCambiada" in estadoDeLaRuta && estadoDeLaRuta.claveCambiada === true;
5716
+ const rutaDeRecuperarClave2 = rutaParaRecuperarClave();
5717
+ const { setIsLoggedIn, checkSession, avisarCierreVoluntario } = useApp();
5493
5718
  const dispatch = useDispatch4();
5494
5719
  const {
5495
5720
  control,
@@ -5521,6 +5746,7 @@ var LoginPage = () => {
5521
5746
  const errorText = "Credenciales incorrectas o error al iniciar sesi\xF3n";
5522
5747
  throw new Error(errorText);
5523
5748
  }
5749
+ avisarCierreVoluntario(false);
5524
5750
  setIsLoggedIn(true);
5525
5751
  await dispatch(fetchClientContext()).unwrap();
5526
5752
  console.log("Login exitoso y clientContext cargado.");
@@ -5560,58 +5786,77 @@ var LoginPage = () => {
5560
5786
  }
5561
5787
  ) }),
5562
5788
  /* @__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,
5789
+ /* @__PURE__ */ jsxs23(
5790
+ "form",
5791
+ {
5792
+ onSubmit: handleSubmit(onSubmit),
5793
+ className: "space-y-4",
5794
+ onKeyDown: (evento) => {
5795
+ if (evento.key !== "Enter" || evento.shiftKey || evento.ctrlKey || evento.altKey || evento.metaKey) return;
5796
+ if (!(evento.target instanceof HTMLInputElement)) return;
5797
+ evento.preventDefault();
5798
+ if (!isSubmitting) void handleSubmit(onSubmit)();
5799
+ },
5800
+ children: [
5801
+ /* @__PURE__ */ jsxs23("div", { children: [
5802
+ /* @__PURE__ */ jsx30("label", { htmlFor: "username", children: /* @__PURE__ */ jsx30(Typography16, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Usuario" }) }),
5803
+ /* @__PURE__ */ jsx30(
5804
+ Controller,
5573
5805
  {
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
5806
+ name: "username",
5807
+ control,
5808
+ render: ({ field }) => /* @__PURE__ */ jsx30(
5809
+ TextField3,
5810
+ {
5811
+ ...field,
5812
+ type: "text",
5813
+ id: "username",
5814
+ autoComplete: "username",
5815
+ placeholder: "Ingresa tu usuario",
5816
+ fullWidth: true,
5817
+ margin: "normal",
5818
+ error: !!errors.username,
5819
+ helperText: errors.username?.message,
5820
+ disabled: isSubmitting
5821
+ }
5822
+ )
5582
5823
  }
5583
5824
  )
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,
5825
+ ] }),
5826
+ /* @__PURE__ */ jsxs23("div", { children: [
5827
+ /* @__PURE__ */ jsx30("label", { htmlFor: "password", children: /* @__PURE__ */ jsx30(Typography16, { variant: "subtitle1", component: "div", sx: { fontWeight: "medium" }, children: "Contrase\xF1a" }) }),
5828
+ /* @__PURE__ */ jsx30(
5829
+ Controller,
5596
5830
  {
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
5831
+ name: "password",
5832
+ control,
5833
+ render: ({ field }) => /* @__PURE__ */ jsx30(
5834
+ TextField3,
5835
+ {
5836
+ ...field,
5837
+ type: "password",
5838
+ id: "password",
5839
+ autoComplete: "current-password",
5840
+ placeholder: "Ingresa tu contrase\xF1a",
5841
+ fullWidth: true,
5842
+ margin: "normal",
5843
+ error: !!errors.password,
5844
+ helperText: errors.password?.message,
5845
+ disabled: isSubmitting
5846
+ }
5847
+ )
5605
5848
  }
5606
5849
  )
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
- ] }),
5850
+ ] }),
5851
+ /* @__PURE__ */ jsx30(Button10, { type: "submit", fullWidth: true, variant: "contained", color: "primary", disabled: isSubmitting, sx: { mt: 2 }, children: isSubmitting ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
5852
+ /* @__PURE__ */ jsx30(CircularProgress6, { size: 20, sx: { mr: 1 } }),
5853
+ "Iniciando sesi\xF3n..."
5854
+ ] }) : "Iniciar Sesi\xF3n" })
5855
+ ]
5856
+ }
5857
+ ),
5858
+ 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" }) }),
5859
+ claveCambiada && !loginError && /* @__PURE__ */ jsx30(Alert3, { severity: "success", sx: { mt: 2 }, children: "La contrase\xF1a se cambi\xF3. Ingres\xE1 con la clave nueva." }),
5615
5860
  loginError && /* @__PURE__ */ jsxs23(Alert3, { severity: "error", sx: { mt: 2, maxHeight: "200px" }, children: [
5616
5861
  " ",
5617
5862
  /* @__PURE__ */ jsx30(AlertTitle, { children: "Error" }),
@@ -5626,11 +5871,11 @@ var LoginPage = () => {
5626
5871
  var LoginPage_default = LoginPage;
5627
5872
 
5628
5873
  // src/components/ProcedureForm.tsx
5629
- import React23, { useState as useState19, useCallback as useCallback15, useMemo as useMemo12 } from "react";
5874
+ import React23, { useState as useState20, useCallback as useCallback15, useMemo as useMemo13 } from "react";
5630
5875
  import { useParams } from "react-router-dom";
5631
5876
  import {
5632
- TextField as TextField3,
5633
- Button as Button12,
5877
+ TextField as TextField4,
5878
+ Button as Button11,
5634
5879
  FormControl as FormControl3,
5635
5880
  InputLabel as InputLabel2,
5636
5881
  Select as Select2,
@@ -5673,17 +5918,17 @@ var ProcedureForm = () => {
5673
5918
  const { procedureName } = useParams();
5674
5919
  const { clientContext } = useApp();
5675
5920
  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(() => {
5921
+ const [validationErrors, setValidationErrors] = useState20({});
5922
+ const [successMessage, setSuccessMessage] = useState20(null);
5923
+ const [executionLog, setExecutionLog] = useState20([]);
5924
+ const [procedureResultData, setProcedureResultData] = useState20(null);
5925
+ const procedure = useMemo13(() => {
5681
5926
  if (procedureName && clientContext?.procedure) {
5682
5927
  return clientContext.procedure[procedureName];
5683
5928
  }
5684
5929
  return void 0;
5685
5930
  }, [procedureName, clientContext]);
5686
- const [formValues, setFormValues] = useState19(() => {
5931
+ const [formValues, setFormValues] = useState20(() => {
5687
5932
  if (!procedure) return {};
5688
5933
  const initialValues = {};
5689
5934
  procedure.parameters.forEach((param) => {
@@ -5912,13 +6157,13 @@ var ProcedureForm = () => {
5912
6157
  case "tsrange":
5913
6158
  case "time_range":
5914
6159
  case "daterange":
5915
- return /* @__PURE__ */ jsx32(TextField3, { type: "text", value: formValues[param.name] || "", ...commonProps });
6160
+ return /* @__PURE__ */ jsx32(TextField4, { type: "text", value: formValues[param.name] || "", ...commonProps });
5916
6161
  case "decimal":
5917
6162
  case "double":
5918
- return /* @__PURE__ */ jsx32(TextField3, { type: "number", inputProps: { step: "any", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
6163
+ return /* @__PURE__ */ jsx32(TextField4, { type: "number", inputProps: { step: "any", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
5919
6164
  case "integer":
5920
6165
  case "bigint":
5921
- return /* @__PURE__ */ jsx32(TextField3, { type: "number", inputProps: { step: "1", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
6166
+ return /* @__PURE__ */ jsx32(TextField4, { type: "number", inputProps: { step: "1", style: { fontSize: "0.8rem" } }, value: formValues[param.name] || "", ...commonProps });
5922
6167
  case "boolean":
5923
6168
  return /* @__PURE__ */ jsx32(
5924
6169
  FormControlLabel3,
@@ -5941,10 +6186,10 @@ var ProcedureForm = () => {
5941
6186
  case "date":
5942
6187
  case "timestamp":
5943
6188
  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" } } });
6189
+ 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
6190
  case "jsonb":
5946
6191
  case "jsona":
5947
- return /* @__PURE__ */ jsx32(TextField3, { multiline: true, rows: 5, value: formValues[param.name] || "", ...commonProps, InputProps: { sx: { fontSize: "0.8rem" } } });
6192
+ return /* @__PURE__ */ jsx32(TextField4, { multiline: true, rows: 5, value: formValues[param.name] || "", ...commonProps, InputProps: { sx: { fontSize: "0.8rem" } } });
5948
6193
  case "bytea":
5949
6194
  return /* @__PURE__ */ jsxs25(FormControl3, { fullWidth: true, margin: "dense", error: hasError, required: commonProps.required, children: [
5950
6195
  /* @__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 +6210,10 @@ var ProcedureForm = () => {
5965
6210
  (param.description || errorMessage) && /* @__PURE__ */ jsx32(FormHelperText2, { sx: { fontSize: "0.7rem" }, children: errorMessage || param.description })
5966
6211
  ] });
5967
6212
  default:
5968
- return /* @__PURE__ */ jsx32(TextField3, { type: "text", value: formValues[param.name] || "", ...commonProps });
6213
+ return /* @__PURE__ */ jsx32(TextField4, { type: "text", value: formValues[param.name] || "", ...commonProps });
5969
6214
  }
5970
6215
  }, [formValues, handleChange, apiLoading, validationErrors]);
5971
- const ResultDisplayContent = useMemo12(() => {
6216
+ const ResultDisplayContent = useMemo13(() => {
5972
6217
  if (!procedureResultData || !procedure?.resultOk) return null;
5973
6218
  const ComponentToRender = resultsOk[procedure.resultOk];
5974
6219
  if (ComponentToRender) {
@@ -6032,7 +6277,7 @@ var ProcedureForm = () => {
6032
6277
  /* @__PURE__ */ jsxs25("form", { onSubmit: handleSubmit, children: [
6033
6278
  procedure.parameters.map((param) => /* @__PURE__ */ jsx32("div", { children: renderInputField(param) }, param.name)),
6034
6279
  /* @__PURE__ */ jsx32(
6035
- Button12,
6280
+ Button11,
6036
6281
  {
6037
6282
  type: "submit",
6038
6283
  variant: "contained",
@@ -6093,10 +6338,10 @@ import { PersistGate } from "redux-persist/integration/react";
6093
6338
 
6094
6339
  // src/components/PrivateRoute.tsx
6095
6340
  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";
6341
+ import { Navigate as Navigate2, useLocation as useLocation4 } from "react-router-dom";
6342
+ import { Fragment as Fragment7, jsx as jsx33 } from "react/jsx-runtime";
6098
6343
  var PrivateRoute = ({ children }) => {
6099
- const location = useLocation3();
6344
+ const location = useLocation4();
6100
6345
  const dispatch = useAppDispatch();
6101
6346
  const clientContextStatus = useClientContextStatus();
6102
6347
  const isAuthenticated = clientContextStatus === "succeeded";
@@ -6104,7 +6349,7 @@ var PrivateRoute = ({ children }) => {
6104
6349
  console.log(`[PrivateRoute] Render: Path=${location.pathname}, AuthStatus=${clientContextStatus}, IsAuthenticated=${isAuthenticated}, IsLoadingAuth=${isLoadingAuth}`);
6105
6350
  useEffect11(() => {
6106
6351
  console.log(`[PrivateRoute] Effect: Path=${location.pathname}, AuthStatus=${clientContextStatus}, IsAuthenticated=${isAuthenticated}, IsLoadingAuth=${isLoadingAuth}`);
6107
- if (!isAuthenticated && !isLoadingAuth && location.pathname !== "/login") {
6352
+ if (!isAuthenticated && !isLoadingAuth && location.pathname !== "/login" && !huboCierreVoluntario()) {
6108
6353
  const path = location.pathname + location.search + location.hash;
6109
6354
  console.log(`[PrivateRoute] ACTION: Dispatching setRedirectPath(${path})`);
6110
6355
  dispatch(setRedirectPath(path));
@@ -6125,29 +6370,33 @@ var PrivateRoute = ({ children }) => {
6125
6370
  return /* @__PURE__ */ jsx33(Navigate2, { to: "/login", replace: true });
6126
6371
  }
6127
6372
  console.log(`[PrivateRoute] User authenticated, rendering children.`);
6128
- return /* @__PURE__ */ jsx33(Fragment6, { children });
6373
+ return /* @__PURE__ */ jsx33(Fragment7, { children });
6129
6374
  };
6130
6375
  var PrivateRoute_default = PrivateRoute;
6131
6376
 
6132
6377
  // 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";
6378
+ import { useEffect as useEffect12, useRef as useRef10 } from "react";
6379
+ import { useLocation as useLocation5, useNavigate as useNavigate5 } from "react-router-dom";
6135
6380
  var InitialRedirectHandler = () => {
6136
- const location = useLocation4();
6137
- const navigate = useNavigate4();
6381
+ const location = useLocation5();
6382
+ const navigate = useNavigate5();
6138
6383
  const dispatch = useAppDispatch();
6139
6384
  const redirectPath = useRedirectPath();
6140
6385
  const clientContextStatus = useClientContextStatus();
6386
+ const username = useAppSelector((state) => state.clientContext.username);
6387
+ const redirectUsername = useAppSelector((state) => state.router.redirectUsername);
6141
6388
  useEffect12(() => {
6142
6389
  dispatch(setCurrentPath(location.pathname + location.search));
6143
6390
  }, [location, dispatch]);
6144
- const navegandoRef = useRef9(false);
6391
+ const navegandoRef = useRef10(false);
6145
6392
  useEffect12(() => {
6146
6393
  navegandoRef.current = false;
6147
6394
  }, [location.pathname]);
6148
6395
  useEffect12(() => {
6149
6396
  if (clientContextStatus === "succeeded") {
6150
- if (redirectPath && redirectPath !== "/login") {
6397
+ if (redirectPath && redirectPath !== "/login" && redirectUsername && redirectUsername !== username) {
6398
+ dispatch(setRedirectPath(null));
6399
+ } else if (redirectPath && redirectPath !== "/login") {
6151
6400
  console.log("Redirigiendo a la ruta guardada:", redirectPath);
6152
6401
  navegandoRef.current = true;
6153
6402
  navigate(redirectPath);
@@ -6157,15 +6406,15 @@ var InitialRedirectHandler = () => {
6157
6406
  navigate("/");
6158
6407
  }
6159
6408
  }
6160
- }, [clientContextStatus, redirectPath, location.pathname, navigate, dispatch]);
6409
+ }, [clientContextStatus, redirectPath, redirectUsername, username, location.pathname, navigate, dispatch]);
6161
6410
  return null;
6162
6411
  };
6163
6412
  var InitialRedirectHandler_default = InitialRedirectHandler;
6164
6413
 
6165
6414
  // 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";
6415
+ import React26, { useState as useState21, useCallback as useCallback16, useMemo as useMemo14 } from "react";
6416
+ import { useParams as useParams2, useLocation as useLocation6 } from "react-router-dom";
6417
+ import { Box as Box21, Alert as Alert5, Typography as Typography19, Tabs, Tab, IconButton as IconButton9 } from "@mui/material";
6169
6418
  import CloseIcon2 from "@mui/icons-material/Close";
6170
6419
  import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
6171
6420
  var GenericDataGridPage = ({
@@ -6175,11 +6424,11 @@ var GenericDataGridPage = ({
6175
6424
  }) => {
6176
6425
  const { tableName: urlTableName } = useParams2();
6177
6426
  const effectiveTableName = initialTableName || urlTableName;
6178
- const [tabs, setTabs] = useState20([]);
6179
- const [activeTabIdx, setActiveTabIdx] = useState20(0);
6427
+ const [tabs, setTabs] = useState21([]);
6428
+ const [activeTabIdx, setActiveTabIdx] = useState21(0);
6180
6429
  const ffKey = JSON.stringify(initialFixedFields);
6181
- const location = useLocation5();
6182
- const parametros = useMemo13(() => parametrosRecibidos ?? parametrosDeTabla(void 0, location.search), [parametrosRecibidos, location.search]);
6430
+ const location = useLocation6();
6431
+ const parametros = useMemo14(() => parametrosRecibidos ?? parametrosDeTabla(void 0, location.search), [parametrosRecibidos, location.search]);
6183
6432
  const fixedFieldsIniciales = initialFixedFields.length > 0 ? initialFixedFields : parametros.fixedFields ?? [];
6184
6433
  const parametrosKey = JSON.stringify(parametros);
6185
6434
  React26.useEffect(() => {
@@ -6226,7 +6475,7 @@ var GenericDataGridPage = ({
6226
6475
  {
6227
6476
  label: /* @__PURE__ */ jsxs26(Box21, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
6228
6477
  /* @__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" }) })
6478
+ index !== 0 && /* @__PURE__ */ jsx34(IconButton9, { size: "small", onClick: (e) => handleCloseTab(index, e), children: /* @__PURE__ */ jsx34(CloseIcon2, { fontSize: "inherit" }) })
6230
6479
  ] })
6231
6480
  },
6232
6481
  tab.id
@@ -6258,27 +6507,27 @@ var GenericDataGridPage = ({
6258
6507
  var GenericDataGridPage_default = GenericDataGridPage;
6259
6508
 
6260
6509
  // src/pages/PantallaDeRuta.tsx
6261
- import { useMemo as useMemo14 } from "react";
6510
+ import { useMemo as useMemo15 } from "react";
6262
6511
  import { useSearchParams } from "react-router-dom";
6263
6512
  import { Box as Box22 } from "@mui/material";
6264
6513
  import { jsx as jsx35 } from "react/jsx-runtime";
6265
6514
  var PantallaDeRuta = ({ screenName }) => {
6266
6515
  const [busqueda] = useSearchParams();
6267
- const params = useMemo14(() => Object.fromEntries(busqueda.entries()), [busqueda]);
6516
+ const params = useMemo15(() => Object.fromEntries(busqueda.entries()), [busqueda]);
6268
6517
  const Pantalla = wScreens[screenName];
6269
6518
  return /* @__PURE__ */ jsx35(Box22, { sx: { pt: 3, pl: 4 }, children: Pantalla ? /* @__PURE__ */ jsx35(Pantalla, { screenName, params }) : /* @__PURE__ */ jsx35(FallbackWScreen, { screenName }) });
6270
6519
  };
6271
6520
 
6272
6521
  // src/pages/MenuTablePage.tsx
6273
- import { useMemo as useMemo15 } from "react";
6274
- import { useLocation as useLocation6, useParams as useParams3 } from "react-router-dom";
6522
+ import { useMemo as useMemo16 } from "react";
6523
+ import { useLocation as useLocation7, useParams as useParams3 } from "react-router-dom";
6275
6524
  import { Box as Box23, Alert as Alert6, Typography as Typography20 } from "@mui/material";
6276
6525
  import { jsx as jsx36, jsxs as jsxs27 } from "react/jsx-runtime";
6277
6526
  var MenuTablePage = () => {
6278
6527
  const { "*": menuName } = useParams3();
6279
6528
  const { clientContext } = useApp();
6280
- const { search } = useLocation6();
6281
- const { currentTableName, currentFixedFields, parametros } = useMemo15(() => {
6529
+ const { search } = useLocation7();
6530
+ const { currentTableName, currentFixedFields, parametros } = useMemo16(() => {
6282
6531
  let tableName = void 0;
6283
6532
  let fixedFields = [];
6284
6533
  let parametros2 = {};
@@ -6316,8 +6565,8 @@ var MenuTablePage = () => {
6316
6565
  var MenuTablePage_default = MenuTablePage;
6317
6566
 
6318
6567
  // 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";
6568
+ import { useEffect as useEffect15, useRef as useRef11, useState as useState22 } from "react";
6569
+ import { Navigate as Navigate3, useLocation as useLocation8, useParams as useParams4 } from "react-router-dom";
6321
6570
  import { Alert as Alert7, Box as Box25, CircularProgress as CircularProgress8 } from "@mui/material";
6322
6571
 
6323
6572
  // src/puente/clienteBP.ts
@@ -6520,13 +6769,6 @@ function resolverPantallaBP(myOwn, nombre, contenedor) {
6520
6769
  return null;
6521
6770
  }
6522
6771
 
6523
- // src/puente/habilitacion.ts
6524
- var habilitado = false;
6525
- var habilitarPuenteBP = () => {
6526
- habilitado = true;
6527
- };
6528
- var puenteBPHabilitado = () => habilitado;
6529
-
6530
6772
  // src/puente/PantallaBP.tsx
6531
6773
  import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
6532
6774
  function buscarItemDeMenu(items, tipo) {
@@ -6541,10 +6783,10 @@ function buscarItemDeMenu(items, tipo) {
6541
6783
  }
6542
6784
  var PantallaBP = ({ nombre, direccion }) => {
6543
6785
  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);
6786
+ const [cargando, setCargando] = useState22(true);
6787
+ const [error, setError] = useState22(null);
6788
+ const [noExiste, setNoExiste] = useState22(false);
6789
+ const contenedor = useRef11(null);
6548
6790
  const menu = clientContext.menu;
6549
6791
  useEffect15(() => {
6550
6792
  const elemento = contenedor.current;
@@ -6590,14 +6832,14 @@ var PantallaBP = ({ nombre, direccion }) => {
6590
6832
  };
6591
6833
  var PantallaDeRespaldo = () => {
6592
6834
  const { screenName = "" } = useParams4();
6593
- const { search } = useLocation7();
6835
+ const { search } = useLocation8();
6594
6836
  if (!puenteBPHabilitado()) {
6595
6837
  return /* @__PURE__ */ jsx38(Box25, { sx: { p: 3 }, children: /* @__PURE__ */ jsx38(FallbackWScreen, { screenName }) });
6596
6838
  }
6597
6839
  return /* @__PURE__ */ jsx38(Navigate3, { to: rutaDePantallaBP(screenName, search), replace: true });
6598
6840
  };
6599
6841
  var PaginaBP = () => {
6600
- const { hash, search } = useLocation7();
6842
+ const { hash, search } = useLocation8();
6601
6843
  const direccion = hash || search;
6602
6844
  const nombre = new URLSearchParams(direccion.replace(/^[#?]/, "")).get("w");
6603
6845
  if (!puenteBPHabilitado()) return /* @__PURE__ */ jsx38(Navigate3, { to: "/", replace: true });
@@ -6610,14 +6852,14 @@ var PaginaBP = () => {
6610
6852
  };
6611
6853
 
6612
6854
  // src/puente/GrillaBP.tsx
6613
- import { useEffect as useEffect16, useRef as useRef11, useState as useState22 } from "react";
6855
+ import { useEffect as useEffect16, useRef as useRef12, useState as useState23 } from "react";
6614
6856
  import { useParams as useParams5 } from "react-router-dom";
6615
6857
  import { Alert as Alert8, Box as Box26, CircularProgress as CircularProgress9 } from "@mui/material";
6616
6858
  import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
6617
6859
  var GrillaBP = ({ tabla, fixedFields }) => {
6618
- const contenedor = useRef11(null);
6619
- const [cargando, setCargando] = useState22(true);
6620
- const [error, setError] = useState22(null);
6860
+ const contenedor = useRef12(null);
6861
+ const [cargando, setCargando] = useState23(true);
6862
+ const [error, setError] = useState23(null);
6621
6863
  const clave = JSON.stringify(fixedFields ?? []);
6622
6864
  useEffect16(() => {
6623
6865
  const elemento = contenedor.current;
@@ -6657,7 +6899,7 @@ var PaginaGrillaBP = () => {
6657
6899
 
6658
6900
  // src/puente/CapturaBP.tsx
6659
6901
  import { useEffect as useEffect17 } from "react";
6660
- import { useNavigate as useNavigate5 } from "react-router-dom";
6902
+ import { useNavigate as useNavigate6 } from "react-router-dom";
6661
6903
 
6662
6904
  // src/puente/capturaDeNavegacion.ts
6663
6905
  function apiDeNavegacion() {
@@ -6702,16 +6944,128 @@ function capturarNavegacionBP(urlBackend, navegar) {
6702
6944
 
6703
6945
  // src/puente/CapturaBP.tsx
6704
6946
  var CapturaBP = () => {
6705
- const navigate = useNavigate5();
6947
+ const navigate = useNavigate6();
6706
6948
  useEffect17(() => capturarNavegacionBP(envConfig.backendUrl, navigate), [navigate]);
6707
6949
  return null;
6708
6950
  };
6709
6951
 
6952
+ // src/pages/CambiarClavePage.tsx
6953
+ import { useState as useState24 } from "react";
6954
+ import { Alert as Alert9, Box as Box27, Button as Button12, CircularProgress as CircularProgress10, TextField as TextField5, Typography as Typography21 } from "@mui/material";
6955
+ import { useNavigate as useNavigate7 } from "react-router-dom";
6956
+ import { useDispatch as useDispatch5 } from "react-redux";
6957
+
6958
+ // src/utils/cambiarClave.ts
6959
+ var MENSAJE_POR_OMISION = "No se pudo cambiar la contrase\xF1a";
6960
+ function resultadoDeCambioDeClave(urlFinal, html) {
6961
+ if (/\/login\/?(\?.*)?$/.test(urlFinal)) return { ok: true };
6962
+ const mensajes = Array.from(new DOMParser().parseFromString(html, "text/html").querySelectorAll(".error-message")).map((elemento) => (elemento.textContent ?? "").trim()).filter((mensaje) => mensaje !== "");
6963
+ return { ok: false, mensaje: mensajes.join("\n") || MENSAJE_POR_OMISION };
6964
+ }
6965
+
6966
+ // src/pages/CambiarClavePage.tsx
6967
+ import { jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
6968
+ var CambiarClavePage = () => {
6969
+ const { clientContext, setIsLoggedIn, avisarCierreVoluntario } = useApp();
6970
+ const dispatch = useDispatch5();
6971
+ const navigate = useNavigate7();
6972
+ const [claveActual, setClaveActual] = useState24("");
6973
+ const [claveNueva, setClaveNueva] = useState24("");
6974
+ const [claveRepetida, setClaveRepetida] = useState24("");
6975
+ const [error, setError] = useState24(null);
6976
+ const [enviando, setEnviando] = useState24(false);
6977
+ const enviar = async (evento) => {
6978
+ evento.preventDefault();
6979
+ setError(null);
6980
+ if (claveNueva !== claveRepetida) {
6981
+ setError("La clave nueva y su repetici\xF3n no coinciden");
6982
+ return;
6983
+ }
6984
+ setEnviando(true);
6985
+ avisarCierreVoluntario(true);
6986
+ try {
6987
+ const respuesta = await fetchApi("/chpass", {
6988
+ method: "POST",
6989
+ body: new URLSearchParams({ oldPassword: claveActual, newPassword: claveNueva, repPassword: claveRepetida })
6990
+ });
6991
+ const resultado = resultadoDeCambioDeClave(respuesta.url, await respuesta.text());
6992
+ if (!resultado.ok) {
6993
+ avisarCierreVoluntario(false);
6994
+ setError(resultado.mensaje);
6995
+ return;
6996
+ }
6997
+ setIsLoggedIn(false);
6998
+ dispatch(clearClientContext());
6999
+ dispatch(setRedirectPath(null));
7000
+ navigate("/login", { replace: true, state: { claveCambiada: true } });
7001
+ } catch (err) {
7002
+ avisarCierreVoluntario(false);
7003
+ setError(mensajeDeError(err, "No se pudo cambiar la contrase\xF1a"));
7004
+ } finally {
7005
+ setEnviando(false);
7006
+ }
7007
+ };
7008
+ 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: [
7009
+ /* @__PURE__ */ jsx40(Typography21, { variant: "h5", gutterBottom: true, children: "Cambiar contrase\xF1a" }),
7010
+ /* @__PURE__ */ jsx40(TextField5, { label: "Usuario", value: clientContext?.username ?? "", fullWidth: true, margin: "normal", disabled: true, autoComplete: "username" }),
7011
+ /* @__PURE__ */ jsx40(
7012
+ TextField5,
7013
+ {
7014
+ label: "Contrase\xF1a actual",
7015
+ type: "password",
7016
+ value: claveActual,
7017
+ onChange: (e) => setClaveActual(e.target.value),
7018
+ fullWidth: true,
7019
+ margin: "normal",
7020
+ autoFocus: true,
7021
+ required: true,
7022
+ autoComplete: "current-password",
7023
+ disabled: enviando
7024
+ }
7025
+ ),
7026
+ /* @__PURE__ */ jsx40(
7027
+ TextField5,
7028
+ {
7029
+ label: "Contrase\xF1a nueva",
7030
+ type: "password",
7031
+ value: claveNueva,
7032
+ onChange: (e) => setClaveNueva(e.target.value),
7033
+ fullWidth: true,
7034
+ margin: "normal",
7035
+ required: true,
7036
+ autoComplete: "new-password",
7037
+ disabled: enviando
7038
+ }
7039
+ ),
7040
+ /* @__PURE__ */ jsx40(
7041
+ TextField5,
7042
+ {
7043
+ label: "Repetir contrase\xF1a nueva",
7044
+ type: "password",
7045
+ value: claveRepetida,
7046
+ onChange: (e) => setClaveRepetida(e.target.value),
7047
+ fullWidth: true,
7048
+ margin: "normal",
7049
+ required: true,
7050
+ autoComplete: "new-password",
7051
+ disabled: enviando
7052
+ }
7053
+ ),
7054
+ error && /* @__PURE__ */ jsx40(Alert9, { severity: "error", sx: { mt: 1, whiteSpace: "pre-line" }, children: error }),
7055
+ /* @__PURE__ */ jsxs30(Box27, { sx: { display: "flex", gap: 1, justifyContent: "flex-end", mt: 2 }, children: [
7056
+ /* @__PURE__ */ jsx40(Button12, { onClick: () => navigate(-1), disabled: enviando, children: "Cancelar" }),
7057
+ /* @__PURE__ */ jsx40(Button12, { type: "submit", variant: "contained", disabled: enviando, children: enviando ? /* @__PURE__ */ jsx40(CircularProgress10, { size: 20 }) : "Cambiar contrase\xF1a" })
7058
+ ] }),
7059
+ /* @__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." })
7060
+ ] }) });
7061
+ };
7062
+ var CambiarClavePage_default = CambiarClavePage;
7063
+
6710
7064
  // src/App.tsx
6711
7065
  import typeStore6 from "type-store";
6712
- import { Fragment as Fragment7, jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
7066
+ import { Fragment as Fragment8, jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
6713
7067
  var LocationTracker = () => {
6714
- const location = useLocation8();
7068
+ const location = useLocation9();
6715
7069
  const dispatch = useAppDispatch();
6716
7070
  useEffect18(() => {
6717
7071
  dispatch(setCurrentPath(location.pathname + location.search));
@@ -6719,45 +7073,46 @@ var LocationTracker = () => {
6719
7073
  return null;
6720
7074
  };
6721
7075
  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, {}) }),
7076
+ return /* @__PURE__ */ jsxs31(Fragment8, { children: [
7077
+ /* @__PURE__ */ jsx41(InitialRedirectHandler_default, {}),
7078
+ puenteBPHabilitado() && /* @__PURE__ */ jsx41(CapturaBP, {}),
7079
+ /* @__PURE__ */ jsxs31(Routes, { children: [
7080
+ /* @__PURE__ */ jsxs31(Route, { element: UnloggedLayout ? /* @__PURE__ */ jsx41(UnloggedLayout, {}) : /* @__PURE__ */ jsx41(Outlet2, {}), children: [
7081
+ /* @__PURE__ */ jsx41(Route, { path: "/login", element: /* @__PURE__ */ jsx41(LoginPage_default, {}) }),
7082
+ /* @__PURE__ */ jsx41(Route, { path: "/logout", element: /* @__PURE__ */ jsx41(LogoutPage_default, {}) }),
6729
7083
  myUnloggedRoutes
6730
7084
  ] }),
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(
7085
+ /* @__PURE__ */ jsxs31(Route, { element: /* @__PURE__ */ jsx41(PrivateRoute_default, { children: /* @__PURE__ */ jsx41(Layout, {}) }), children: [
7086
+ /* @__PURE__ */ jsx41(Route, { path: "/", element: /* @__PURE__ */ jsx41(PantallaInicial_default, {}) }),
7087
+ /* @__PURE__ */ jsx41(Route, { path: "/table/:tableName", element: /* @__PURE__ */ jsx41(GenericDataGridPage_default, {}) }),
7088
+ /* @__PURE__ */ jsx41(Route, { path: "/menu/*", element: /* @__PURE__ */ jsx41(MenuTablePage_default, {}) }),
7089
+ /* @__PURE__ */ jsx41(Route, { path: "/procedures/:procedureName", element: /* @__PURE__ */ jsx41(ProcedureForm_default, {}) }),
7090
+ /* @__PURE__ */ jsx41(Route, { path: "/chpass", element: /* @__PURE__ */ jsx41(CambiarClavePage_default, {}) }),
7091
+ Object.keys(wScreens).map((screenName) => /* @__PURE__ */ jsx41(
6737
7092
  Route,
6738
7093
  {
6739
7094
  path: `/wScreens/${screenName}`,
6740
- element: /* @__PURE__ */ jsx40(PantallaDeRuta, { screenName })
7095
+ element: /* @__PURE__ */ jsx41(PantallaDeRuta, { screenName })
6741
7096
  },
6742
7097
  screenName
6743
7098
  )),
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, {}) }),
7099
+ /* @__PURE__ */ jsx41(Route, { path: "/wScreens-fallback/:screenName", element: /* @__PURE__ */ jsx41(PantallaDeRespaldo, {}) }),
7100
+ puenteBPHabilitado() && /* @__PURE__ */ jsx41(Route, { path: "/bp/menu", element: /* @__PURE__ */ jsx41(PaginaBP, {}) }),
7101
+ puenteBPHabilitado() && /* @__PURE__ */ jsx41(Route, { path: "/bp/table/:tableName", element: /* @__PURE__ */ jsx41(PaginaGrillaBP, {}) }),
6747
7102
  myRoutes,
6748
- /* @__PURE__ */ jsx40(Route, { path: "*", element: /* @__PURE__ */ jsx40("div", { style: { marginTop: "20px", marginLeft: "10px" }, children: "404 - Recurso No Encontrado" }) })
7103
+ /* @__PURE__ */ jsx41(Route, { path: "*", element: /* @__PURE__ */ jsx41("div", { style: { marginTop: "20px", marginLeft: "10px" }, children: "404 - Recurso No Encontrado" }) })
6749
7104
  ] }),
6750
- /* @__PURE__ */ jsx40(Route, { path: "*", element: /* @__PURE__ */ jsx40(Navigate4, { to: "/", replace: true }) })
7105
+ /* @__PURE__ */ jsx41(Route, { path: "*", element: /* @__PURE__ */ jsx41(Navigate4, { to: "/", replace: true }) })
6751
7106
  ] })
6752
7107
  ] });
6753
7108
  }
6754
7109
  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, {}),
7110
+ return /* @__PURE__ */ jsx41(Provider, { store, children: /* @__PURE__ */ jsx41(PersistGate, { loading: null, persistor, children: /* @__PURE__ */ jsxs31(BrowserRouter, { basename: rutaBase, children: [
7111
+ /* @__PURE__ */ jsx41(LocationTracker, {}),
6757
7112
  " ",
6758
- /* @__PURE__ */ jsx40(AppProvider, { publicPaths, children: /* @__PURE__ */ jsxs30(SnackbarProvider, { children: [
7113
+ /* @__PURE__ */ jsx41(AppProvider, { publicPaths, children: /* @__PURE__ */ jsxs31(SnackbarProvider, { children: [
6759
7114
  children,
6760
- /* @__PURE__ */ jsx40(SessionExpiredMessage_default, {})
7115
+ /* @__PURE__ */ jsx41(SessionExpiredMessage_default, {})
6761
7116
  ] }) })
6762
7117
  ] }) }) });
6763
7118
  };
@@ -6769,9 +7124,10 @@ var App = ({
6769
7124
  myClientSides,
6770
7125
  myResultsOk,
6771
7126
  myConditions,
6772
- puenteBP
7127
+ puenteBP,
7128
+ recuperarClave
6773
7129
  }) => {
6774
- useMemo16(() => {
7130
+ useMemo17(() => {
6775
7131
  if (myClientSides) {
6776
7132
  extendClientSides(myClientSides);
6777
7133
  }
@@ -6787,7 +7143,8 @@ var App = ({
6787
7143
  if (puenteBP) {
6788
7144
  habilitarPuenteBP();
6789
7145
  }
6790
- }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP]);
7146
+ configurarRecuperarClave(recuperarClave);
7147
+ }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP, recuperarClave]);
6791
7148
  useEffect18(() => {
6792
7149
  typeStore6.messages = typeStore6.i18n.messages.es;
6793
7150
  typeStore6.locale = typeStore6.i18n.locale.es;
@@ -6797,11 +7154,11 @@ var App = ({
6797
7154
  link.href = `${envConfig.backendUrl}/img/logo-128.png`;
6798
7155
  document.getElementsByTagName("head")[0].appendChild(link);
6799
7156
  }, []);
6800
- const safePaths = React33.useMemo(() => {
7157
+ const safePaths = React34.useMemo(() => {
6801
7158
  const extracted = extractPathsFromRoutes(myUnloggedRoutes);
6802
7159
  return ["/login", "/logout", ...extracted];
6803
7160
  }, [myUnloggedRoutes]);
6804
- return /* @__PURE__ */ jsx40(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ jsx40(
7161
+ return /* @__PURE__ */ jsx41(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ jsx41(
6805
7162
  FrontendPlusReactRoutes,
6806
7163
  {
6807
7164
  myRoutes,