frontend-plus-react 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md ADDED
@@ -0,0 +1,153 @@
1
+ # CLAUDE.md — frontend-plus-react
2
+
3
+ Librería React (MUI + react-data-grid) para consumir backends hechos con `backend-plus` sin reescribir el frontend de cada aplicación: login, menú, grillas editables de cualquier tabla (con sus detalles anidados), formularios de procedimientos y editor vertical. Cada aplicación aporta solo lo propio (wScreens, clientSides, resultados de procedimientos) mediante `extendWScreens`, `extendClientSides` y `extendResultsOk`.
4
+
5
+ Se consume desde las aplicaciones (`mi-frontend`, `dmencu-frontend`, y las que vengan) como paquete npm o enlazado con `npm link`. Esta librería **no** es el frontend de `backend-plus` (ese es el que sirve el propio framework con `my-tables.js`): reproduce su comportamiento. Ante la duda de cómo debe comportarse algo, la referencia es `backend-plus` (`for-client/my-tables.js`, `unlogged/my-ajax.js`) y los `.d.ts` en `node_modules/backend-plus/lib/backend-plus.d.ts`.
6
+
7
+ ---
8
+
9
+ ## Reglas de código
10
+
11
+ Son las de `dmencu` (que aplican a todo proyecto compartido de esta carpeta), escritas acá porque esta librería no depende de `dmencu` y no puede importar su `CLAUDE.md`:
12
+
13
+ - **Todos los tipos explícitos.** Prohibido `any`, `@ts-ignore`, casts vía `unknown` o magia sin justificación. Hoy el código no tiene ninguno y tiene que seguir así. Si un callback recibe un parámetro, tiparlo.
14
+ - Lo que viene del servidor o de un catch es `unknown`: `mensajeDeError(err)` para el mensaje de un error y `requerido(valor, 'qué faltó')` para algo que tiene que existir (ambos en `utils/errores.ts`). Las filas son `FilaDeDatos`; las filas de resumen (filtros y totales), `FilaDeResumen`; los parámetros de un procedimiento, `ParametrosDeProcedimiento`.
15
+ - `callApi<R>(...)` lleva la forma del resultado de ese procedimiento; la única afirmación de tipo sobre lo que manda el servidor está en `resultadoDeProcedimiento` (`utils/fetchApi.ts`).
16
+ - `type-store` no publica tipos: están declarados en `src/types/type-store.d.ts`, escritos para mudarse tal cual a `types.d.ts` (`modules/type-store/index.d.ts`). Las columnas propias se distinguen con `esColumnaPropia` y `customType`, no con casts.
17
+ - Una excepción acotada: los tests usan dobles de los eventos de react-data-grid con una aserción de tipo, en un único helper por archivo.
18
+ - Si una `interface` no cabe en `FilaDeDatos` (`Record<string, unknown>`), es porque las interfaces no tienen firma de índice: usar `type`.
19
+ - **Preservar el estilo existente** y **no tocar el formato de lo que ya existe**: el diff muestra solo las líneas que cambian de verdad. Sin reindentar, sin tocar espacios finales, sin agregar ni quitar el salto de línea final.
20
+ - **Respetar el fin de línea de cada archivo**: hay archivos con CRLF y otros con LF. Las líneas nuevas llevan el mismo que el resto del archivo (`git ls-files --eol <archivo>`). No editar con `sed -i` ni con herramientas que reescriban el archivo entero. Antes de commitear, `git diff --stat` tiene que coincidir con `git diff --ignore-all-space --ignore-cr-at-eol --stat`; si no, hay formato que revertir.
21
+ - **Nombres de dominio, funciones, variables y comentarios en castellano** (consistente con las aplicaciones y las bases). Los identificadores que son API de terceros se dejan como están.
22
+ - **Compilación sin fallbacks**: `npm run build` y `npm run typecheck` no toleran errores; no agregar `|| echo ...` a los scripts. Un error de tipos se corrige en el código.
23
+ - `npm install` reordena y reformatea todo `package.json`. Después de instalar algo, restaurar el archivo y agregar a mano solo la línea nueva (el `package-lock.json` sí lo maneja npm).
24
+
25
+ ---
26
+
27
+ ## Estructura
28
+
29
+ - `src/index.ts` — lo que exporta la librería. Lo que no está acá es interno.
30
+ - `src/App.tsx` — `App` y `FrontendPlusReactRoutes` (rutas, proveedores).
31
+ - `src/components/grid/` — la grilla:
32
+ - `GenericDataGrid.tsx`: orquesta todo (carga, columnas, detalles anidados, diálogos).
33
+ - `estadoGrilla.ts`: store por grilla para el feedback de guardado y las celdas pendientes (`useSyncExternalStore`).
34
+ - `detalles.tsx`: tipos y contextos de los detalles desplegados dentro de la grilla.
35
+ - `InputRenderer.tsx`: editor de celda y guardado de la fila.
36
+ - `renderers/`: celdas, encabezados, filas de resumen y editores.
37
+ - `src/hooks/grid/` — `useGridDataView` (filtros, orden, totales), `useGridColumns`, `useGridEvents` (teclado y edición), `useGridLayout` (alturas), `useGridActions`, `useGridMenu`.
38
+ - `src/utils/fetchApi.ts` — comunicación con el backend (codificación de parámetros).
39
+ - `src/store/` — Redux: contexto del cliente (menú, procedimientos), ruta y menú.
40
+ - `src/contexts/AppContext.tsx` — sesión (`keep-alive`) y cartel de sesión expirada.
41
+ - `src/components/` — `PrivateRoute`, `InitialRedirectHandler`, `MainLayout`, `ProcedureForm`, etc.
42
+ - `dist/` — salida de `tsup`. Está en `.gitignore`; no se edita ni se versiona.
43
+
44
+ ---
45
+
46
+ ## Cosas que no son obvias (y se rompieron antes)
47
+
48
+ ### Protocolo con el backend
49
+ - Cada parámetro viaja codificado según su `encoding` en `client-setup` (`JSON4all` por omisión, `plain`, `JSON`), igual que `my-ajax.js`; el servidor decodifica con el mismo encoding. Mandar `JSON.stringify` o texto crudo rompe fechas (llegan como string) y convierte textos como `"5"` en números. Los procedimientos `method:'get'` van por la URL.
50
+ - El resultado puede traer líneas de progreso antes de `--`; `parseBackendResponse` se queda con la última parte.
51
+
52
+ ### Rendimiento de la grilla
53
+ - Las **columnas** (`useGridColumns`) no pueden depender de datos, feedback, orden ni filtros: si cambian en cada edición, react-data-grid redibuja todas las celdas. Lo que cambia por celda va al `EstadoGrilla`; la celda (`CeldaDato`) es un componente memorizado que se suscribe solo a lo suyo.
54
+ - No copiar las filas en cada render (`rows.map(...)`): anula la memorización de filas. El estilo por fila va por `rowClass`.
55
+ - No reemplazar la grilla por un spinner mientras hay una llamada en curso; el spinner es solo para la carga inicial.
56
+ - `useGridDataView` deja en su lugar la fila editada cuando cambian pocas filas (no reordena ni vuelve a filtrar en medio de una edición) y recalcula todo cuando cambia mucho (refrescar, importar).
57
+ - Las grillas se cargan con una sola petición por tabla y filtros aunque React monte los efectos dos veces (`cargasEnCurso`).
58
+
59
+ ### Edición y teclado
60
+ - Al editar con un filtro activo, react-data-grid entrega solo las filas visibles: `handleRowsChange` reemplaza las filas editadas dentro de `tableData`, nunca lo pisa con lo recibido.
61
+ - **No esperar la respuesta del servidor para mover el foco** (Enter, Tab, flechas, F4): con la tecla apretada llegan más pulsaciones que respuestas. El cambio de la fila se aplica en el acto y el guardado sigue en segundo plano.
62
+ - **No llamar `onRowChange(fila, true)` del editor después de un `await`**: cierra el editor de la celda que esté seleccionada en ese momento (la siguiente). La fila que devuelve el servidor se aplica sobre los datos (`setTableData`).
63
+ - F4 con la tecla apretada: las pulsaciones que caen en la celda antes de que el editor nuevo tenga el foco se le pasan por `registrarEditor`, o quedan en cola hasta que haya uno. F4 usa el valor que acaba de copiar si la fila de arriba todavía no se redibujó.
64
+
65
+ ### Detalles desplegados (como `backend-plus`)
66
+ - Un detalle es una fila especial (`FilaDetalle`, marcada con un `Symbol`) que se intercala debajo de su fila y ocupa todas las columnas; adentro hay otra `GenericDataGrid` (recursiva). Ctrl+click o botón del medio lo abre en otra pestaña.
67
+ - Alturas: **de arriba hacia abajo**. La grilla principal toma lo que queda de la ventana; cada detalle recibe `altoDisponible` y no puede pasar de ahí, así su pie queda visible. No calcular el tope con el alto medido de la grilla padre: una grilla de pocas filas es baja y aplasta a su detalle.
68
+ - Un detalle con muchas filas muestra `firstDisplayCount` (20) cuando supera `firstDisplayOverLimit` (30), los mismos umbrales de `backend-plus`, y **virtualiza** sus filas; los chicos crecen según su contenido. La grilla principal siempre virtualiza.
69
+ - Anchos: una grilla anidada es tan ancha como sus columnas; si un detalle abierto es más ancho, informa su ancho hacia arriba y el padre se ensancha.
70
+ - Un cache compartido por la jerarquía (`CacheGrillasContext`) permite volver a mostrar un detalle que salió de pantalla por la virtualización sin pedirlo otra vez.
71
+ - El título de una grilla anidada es el de su tabla, igual que el de la principal.
72
+ - **Detalle `wScreen`** (`{wScreen:'ticket', fields:[...]}`, como las anotaciones de un ticket en gestik): se dibuja la pantalla registrada en `myWScreens` y recibe `params` (los fixedFields del detalle como objeto plano, igual que `mainAction(params, div)` de backend-plus). La fila toma el alto de lo que la pantalla dibuja, hasta el máximo disponible, y scrollea por dentro. Las grillas que la pantalla arma (`<GenericDataGrid anidada .../>`) reciben ese máximo por `AltoDisponibleContext`. Con Ctrl+click o el botón del medio se abre en otra pestaña, en `/wScreens/<nombre>?campo=valor` (`PantallaDeRuta` pasa la query string como `params`).
73
+ - **`condition`** de un detalle: el nombre de una función registrada en `myConditions` (o `extendConditions`) que recibe la fila y dice si se muestra el botón; sin ella o si da falso, la celda queda vacía. Si el nombre no está registrado se avisa una vez por consola y se muestra el detalle.
74
+ - Lo que aporta la aplicación (`myWScreens`, `myClientSides`, `myResultsOk`, `myConditions`) se registra **antes del primer render**, no en un efecto: las rutas de las wScreens se arman al renderizar a partir del registro.
75
+ - El `ff` de un ítem de menú puede ser un arreglo `[{fieldName, value}]` (lo que documenta backend-plus) o un objeto `{campo: valor}` (lo que usa dmencu); `fixedFieldsDeMenu` entiende los dos.
76
+
77
+ ### Sesión y arranque
78
+ - Con el estado todavía sin cargar (`idle`), `PrivateRoute` manda a `/login` guardando la ruta pedida; `InitialRedirectHandler` (montado fuera de las rutas privadas) vuelve a esa ruta cuando la sesión resulta válida. Tiene una guarda para no caer en `/home` mientras la ruta guardada se limpia.
79
+ - El cartel «¡Sesión expirada!» solo corresponde si hubo una sesión (guardada de la visita anterior o activa en esta carga). En la primera visita sin sesión, ir al login no lleva cartel.
80
+
81
+ ---
82
+
83
+ ## Puente con el cliente de backend-plus (opcional, `src/puente/`)
84
+
85
+ Para no perder lo que la grilla de `backend-plus` ya hace y que acá todavía no está: la aplicación pasa `puenteBP` a `App` y las wScreens que **no** registró en `myWScreens` se piden al cliente de `backend-plus` (`myOwn.wScreens[nombre]`), en lugar de mostrar «no implementada». `GrillaBP` pone `myOwn.tableGrid` dentro de una página React; `/bp/table/:tableName` sirve para comparar las dos grillas. Lo propio de React siempre tiene prioridad.
86
+
87
+ - **Mismo origen que el backend, obligatoriamente.** Los scripts de `backend-plus` solo se sirven con sesión (sin ella redirigen al login) y su cliente es un singleton global (`myOwn`, `my`). La aplicación se sirve bajo `<base-url>/react/` o, en desarrollo, Vite hace de proxy (ver `mi-frontend`: `VITE_BASE=/ggs2026/react/ VITE_BACKEND_URL=/ggs2026 PROXY_BACKEND=http://localhost:3044`).
88
+ - `cargarClienteBP` lee la página `<base-url>/menu`, saca de ahí los `<script>` y `<link>` (los arma el servidor con `clientIncludes`, en orden) y los carga. Es un atajo: lo sano sería que el servidor exponga esa lista, y eso es cambiar `backend-plus` (consultar antes).
89
+ - Tres cosas que el cliente de `backend-plus` necesita y que el puente hace: un `<base href>` en la base-url (arma sus direcciones relativas a la página), el global `my` igual a `myOwn`, y llamar a `myOwn.autoSetup()` a mano (él lo hace al evento `load`, que ya pasó).
90
+ - Una wScreen de `backend-plus` puede ser una función, un objeto con `mainFunction`, o un objeto con `parameters` y `mainAction` (como `abrir_encuesta` de dmencu: muestra el formulario de parámetros). `resolverPantallaBP` reproduce el `showPage` de my-menu.js para las tres.
91
+ - **Las pantallas de `backend-plus` viven en una sola ruta, `/bp/menu#w=<pantalla>&...`** (`PaginaBP`; `/wScreens-fallback/:nombre` redirige ahí). Se parece a `<base-url>/menu` **a propósito**: BP y las aplicaciones arman direcciones a partir de la ubicación (`location.origin + location.pathname + '/../menu#w=...'` en un `history.replaceState` + `location.reload()`, como el «Cerrar» de una encuesta de dmencu; `location.pathname + '#w=...'` para abrir en otra pestaña) y así caen en esta misma ruta, que lee los parámetros del hash (o de la query) como BP. Con otra ruta, esas direcciones salen de `/react`. En producción, el servidor tiene que devolver el `index.html` también para `/react/bp/menu`.
92
+ - **Lo que BP manda a páginas del backend se resuelve adentro** (`capturaDeNavegacion.ts`, activo mientras hay una pantalla de BP): clics en `<a>` (a nivel documento, también en diálogos) y, donde existe la Navigation API, cualquier `location.href = ...`. `rutaDeEnlaceBP` traduce `<base-url>/menu#w=...` a `/bp/menu#w=...`, y `login`/`logout` a los de la aplicación. El clic se corta con `stopPropagation`: el botón «proceder» de BP tiene además un `onclick` propio (`history.pushState` a `<base-url>/menu#...` y `showPage`) que dejaría la URL fuera de la aplicación y daría un error de BP. Los enlaces `menu#i=...` (ítems del menú de BP, sin `w`) no se traducen. Los parámetros se leen con `myOwn.UriSearchToObject`, no con `URLSearchParams`: `up`, `ff` y `detailing` tienen su propio formato y llegan como objetos.
93
+ - Las wScreens de `backend-plus` dibujan en `#main_layout`: `PantallaBP` le da ese contenedor y le pasa lo que declara su ítem de menú más la query string. Mientras está montada no puede haber otro elemento con ese id.
94
+ - `rutaBase` (`src/rutaBase.ts`) toma el `base` del bundler: es el `basename` del router y el prefijo de las pestañas nuevas (Ctrl+click en un detalle `wScreen`).
95
+ - **No hay un entorno de prueba automático para esto**: jsdom no carga los scripts de `backend-plus`. Los tests cubren la extracción de recursos y la elección de pantalla; el resto se prueba en el navegador contra un backend real (headless: Chrome con `--screenshot` y un proxy chico que inyecte la cookie de sesión).
96
+ - Probado contra ggs (con Chrome controlado por el protocolo de depuración, sin escribir en la base): grilla `usuarios`, la wScreen `ayuda` y `abrir_encuesta` hasta ver el formulario de la encuesta de dmencu dentro de `mi-frontend`. Sin probar: edición, detalles anidados, exportar y la convivencia del CSS global de `backend-plus` con MUI.
97
+
98
+ ---
99
+
100
+ ## Compilación y fuentes en F12
101
+
102
+ - `npm run build` (tsup, config en `tsup.config.ts`) genera `dist/` en ESM y CJS con `.d.ts`. **Sin `--splitting`**: con una sola entrada el `.map` de CJS sale roto. Los `.map` llevan el código fuente adentro (`sourcesContent`): en F12 se ven los `.ts`/`.tsx` originales.
103
+ - Las dependencias y `peerDependencies` quedan externas; las resuelve el bundler de la aplicación.
104
+ - `mi-frontend` tiene un plugin de Vite que encadena el `.map` de esta librería en el build de la aplicación (Rollup no lo hace solo).
105
+ - ⚠️ `npm install` dentro de una aplicación enlazada (`mi-frontend`) **reemplaza el `npm link`** y pisa `dist/` y `package.json` de esta carpeta con lo publicado. Después hay que rehacer el enlace (en Windows, `mklink /J`) y volver a compilar.
106
+
107
+ ---
108
+
109
+ ## Tests
110
+
111
+ - **Framework**: Vitest + jsdom + Testing Library. **Comandos**: `npm test` (una pasada), `npx vitest` (modo vigilancia), `npm run typecheck`.
112
+ - Los tests viven al lado de lo que prueban (`*.test.ts` / `*.test.tsx`) y están en castellano. La preparación común está en `src/test/preparacion.ts`.
113
+ - Se cubren las funcionalidades críticas y, sobre todo, **cada bug que se arregla lleva su test**: se verifica que falle si se reintroduce el bug. Hoy (74 casos): codificación de parámetros, fixedFields de los detalles, detalle wScreen y `condition`, `PantallaDeRuta`, `ff` del menú, el store de la grilla, la vista de datos (orden, filtros, edición en el lugar), la edición con filtro, la cola de F4, la vuelta a la ruta pedida tras el login, el cartel de sesión expirada y los helpers.
114
+ - jsdom no calcula medidas ni trae `ResizeObserver`: lo que depende del layout (alturas, anchos, virtualización) se prueba en el navegador con `mi-frontend`, no en Vitest.
115
+ - Para el mock de `fetch` y de `fetchApi` usar `vi.stubGlobal` / `vi.mock`; no levantar servidores en los tests.
116
+ - Antes de commitear: `npm run typecheck`, `npm test` y `npm run build`.
117
+
118
+ ### Pruebas en el navegador con un backend real
119
+ - `mi-frontend` sirve para probar con una aplicación real: `VITE_BACKEND_URL=/dmencu PROXY_BACKEND=http://localhost:3050 npx vite`. Con una ruta en `VITE_BACKEND_URL`, Vite hace de proxy y el navegador habla con un solo origen (el backend solo habilita CORS si tiene `server.allowedHosts`). En Git Bash agregar `MSYS_NO_PATHCONV=1` o la ruta se convierte en una ruta de Windows.
120
+ - Las credenciales de prueba se las da el usuario en el chat; **no escribirlas en archivos ni en la documentación**. Las pruebas contra una base real son de solo lectura salvo que el usuario diga otra cosa, y lo que se escriba se deja como estaba.
121
+ - Para medir rendimiento usar el build de producción: en desarrollo React valida de más y los tiempos engañan.
122
+
123
+ ---
124
+
125
+ ## Publicación en npm
126
+
127
+ Es automática, igual que `dmencu`: `.github/workflows/publish.yml` llama al workflow reutilizable `codenautas/.github@v3` (`npm ci`, `npm run build`, tests y `npm publish`). No hay token: npm confía en el repo (Trusted Publisher configurado en npmjs.com para `codenautas/frontend-plus-react`, workflow `publish.yml`, sin environment).
128
+
129
+ Para publicar una versión (solo cuando el usuario lo pide):
130
+ 1. Subir la versión: `npm version X.Y.Z --no-git-tag-version` (toca `package.json` y `package-lock.json`; npm no deja republicar un número ya publicado).
131
+ 2. Commit y `git push origin main`.
132
+ 3. Crear y subir el tag: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`. El tag tiene que coincidir con la versión de `package.json` o el workflow falla.
133
+ 4. Verificar en la pestaña Actions de GitHub y con `npm view frontend-plus-react version`. En esta máquina no está `gh`.
134
+
135
+ - Alternativa: correr el workflow a mano desde Actions en `main` (crea él mismo el tag `v<versión>`).
136
+ - Las versiones con `beta`, `alpha` o `rc` en el número se publican con ese dist-tag, no como `latest`.
137
+ - `package.json` necesita el campo `repository`; sin él npm no valida la publicación.
138
+ - `npm ci` exige el lock sincronizado: probar con `npm ci --dry-run` antes de taguear.
139
+
140
+ ---
141
+
142
+ ## Cuándo parar y preguntar
143
+
144
+ - Si hace falta cambiar `backend-plus` para que algo ande: parar. Casi siempre hay un punto de extensión (o el cambio va en el protocolo del cliente).
145
+ - Si una propiedad de `TableDefinition`, `FieldDefinition` o `ProcedureDef` no aparece en `backend-plus.d.ts`: no inventarla.
146
+ - Si el comportamiento observado difiere de `backend-plus`: avisar de la diferencia en lugar de resolverla en silencio.
147
+ - No publicar en npm ni subir tags sin que el usuario lo pida.
148
+
149
+ ---
150
+
151
+ ## Documentación
152
+
153
+ Mantener actualizados este archivo y el `README.md` cuando cambie el comportamiento de la grilla, el protocolo o el arranque. Las decisiones que se aprenden a los golpes (como las de arriba) van en la sección «Cosas que no son obvias», con el porqué.
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(src_exports, {
37
37
  FrontendPlusProviders: () => FrontendPlusProviders,
38
38
  FrontendPlusReactRoutes: () => FrontendPlusReactRoutes,
39
39
  GenericDataGrid: () => GenericDataGrid_default,
40
+ GrillaBP: () => GrillaBP,
40
41
  MainLayout: () => MainLayout_default,
41
42
  SnackbarProvider: () => SnackbarProvider,
42
43
  cambiarGuionesBajosPorEspacios: () => cambiarGuionesBajosPorEspacios,
@@ -46,6 +47,7 @@ __export(src_exports, {
46
47
  extendResultsOk: () => extendResultsOk,
47
48
  extendWScreens: () => extendWScreens,
48
49
  extractPathsFromRoutes: () => extractPathsFromRoutes,
50
+ habilitarPuenteBP: () => habilitarPuenteBP,
49
51
  resultComponents: () => resultsOk,
50
52
  useApiCall: () => useApiCall,
51
53
  useApiCallWithoutSnackbar: () => useApiCallWithoutSnackbar,
@@ -3451,6 +3453,11 @@ var VerticalEditorPage = ({
3451
3453
  ] });
3452
3454
  };
3453
3455
 
3456
+ // src/rutaBase.ts
3457
+ var import_meta2 = {};
3458
+ var baseDelBundler = import_meta2.env?.BASE_URL ?? "/";
3459
+ var rutaBase = baseDelBundler.replace(/\/+$/, "");
3460
+
3454
3461
  // src/components/grid/GenericDataGrid.tsx
3455
3462
  var import_jsx_runtime18 = require("react/jsx-runtime");
3456
3463
  var SUMMARY_ROW_HEIGHT = 35;
@@ -3840,7 +3847,7 @@ var GenericDataGrid = ({
3840
3847
  const abrirWScreenEnPestana = (0, import_react22.useCallback)((row, detailTable) => {
3841
3848
  const parametros = new URLSearchParams();
3842
3849
  calcularFixedFieldsDetalle(detailTable, row).forEach((ff) => parametros.set(ff.fieldName, String(ff.value)));
3843
- window.open(`${window.location.origin}/wScreens/${detailTable.wScreen}?${parametros}`, "_blank");
3850
+ window.open(`${window.location.origin}${rutaBase}/wScreens/${detailTable.wScreen}?${parametros}`, "_blank");
3844
3851
  }, []);
3845
3852
  const alternarDetalle = (0, import_react22.useCallback)((row, rowId, detailTable, enOtraPestana) => {
3846
3853
  if (enOtraPestana && detailTable.wScreen) {
@@ -4183,8 +4190,8 @@ var GenericDataGrid = ({
4183
4190
  var GenericDataGrid_default = GenericDataGrid;
4184
4191
 
4185
4192
  // src/App.tsx
4186
- var import_react35 = __toESM(require("react"), 1);
4187
- var import_react_router_dom12 = require("react-router-dom");
4193
+ var import_react37 = __toESM(require("react"), 1);
4194
+ var import_react_router_dom14 = require("react-router-dom");
4188
4195
 
4189
4196
  // src/components/MainLayout.tsx
4190
4197
  var import_react26 = require("react");
@@ -5368,7 +5375,7 @@ var ProcedureForm_default = ProcedureForm;
5368
5375
 
5369
5376
  // src/App.tsx
5370
5377
  var import_react_redux5 = require("react-redux");
5371
- var import_react36 = require("redux-persist/integration/react");
5378
+ var import_react38 = require("redux-persist/integration/react");
5372
5379
 
5373
5380
  // src/components/PrivateRoute.tsx
5374
5381
  var import_react30 = require("react");
@@ -5599,62 +5606,330 @@ var MenuTablePage = () => {
5599
5606
  };
5600
5607
  var MenuTablePage_default = MenuTablePage;
5601
5608
 
5602
- // src/App.tsx
5609
+ // src/puente/PantallaBP.tsx
5610
+ var import_react35 = require("react");
5611
+ var import_react_router_dom12 = require("react-router-dom");
5603
5612
  var import_material27 = require("@mui/material");
5604
- var import_type_store6 = __toESM(require("type-store"), 1);
5613
+
5614
+ // src/puente/clienteBP.ts
5615
+ function extraerRecursos(html) {
5616
+ const documento = new DOMParser().parseFromString(html, "text/html");
5617
+ const referencias = (selector, atributo) => Array.from(documento.querySelectorAll(selector)).map((elemento) => elemento.getAttribute(atributo)).filter((ruta) => !!ruta);
5618
+ return {
5619
+ hojasDeEstilo: referencias("link[rel=stylesheet]", "href"),
5620
+ scripts: referencias("script[src]", "src")
5621
+ };
5622
+ }
5623
+ function fijarBase(urlBackend) {
5624
+ const href = new URL(urlBackend.replace(/\/*$/, "/"), window.location.href).href;
5625
+ let base = document.querySelector("base");
5626
+ if (!base) {
5627
+ base = document.createElement("base");
5628
+ document.head.prepend(base);
5629
+ }
5630
+ base.href = href;
5631
+ }
5632
+ function cargarScript(ruta) {
5633
+ return new Promise((resolver, rechazar) => {
5634
+ const script = document.createElement("script");
5635
+ script.async = false;
5636
+ script.src = ruta;
5637
+ script.onload = () => resolver();
5638
+ script.onerror = () => rechazar(new Error(`No se pudo cargar ${ruta}`));
5639
+ document.head.appendChild(script);
5640
+ });
5641
+ }
5642
+ var carga = null;
5643
+ function cargarClienteBP(urlBackend) {
5644
+ if (!carga) {
5645
+ carga = cargarSinMemoria(urlBackend).catch((error) => {
5646
+ carga = null;
5647
+ throw error;
5648
+ });
5649
+ }
5650
+ return carga;
5651
+ }
5652
+ async function cargarSinMemoria(urlBackend) {
5653
+ const destino = new URL(urlBackend, window.location.href);
5654
+ if (destino.origin !== window.location.origin) {
5655
+ throw new Error(`El cliente de backend-plus solo se puede usar desde el mismo origen que el backend (${destino.origin} no es ${window.location.origin}).`);
5656
+ }
5657
+ fijarBase(urlBackend);
5658
+ const respuesta = await fetch(`${destino.pathname.replace(/\/*$/, "")}/menu`, { credentials: "include" });
5659
+ if (!respuesta.ok || /\/login\/?$/.test(new URL(respuesta.url).pathname)) {
5660
+ throw new Error("No hay una sesi\xF3n iniciada en el backend.");
5661
+ }
5662
+ const recursos = extraerRecursos(await respuesta.text());
5663
+ recursos.hojasDeEstilo.forEach((ruta) => {
5664
+ const enlace = document.createElement("link");
5665
+ enlace.rel = "stylesheet";
5666
+ enlace.href = ruta;
5667
+ document.head.appendChild(enlace);
5668
+ });
5669
+ for (const ruta of recursos.scripts) {
5670
+ await cargarScript(ruta);
5671
+ }
5672
+ const myOwn = window.myOwn;
5673
+ if (!myOwn) throw new Error("El backend no defini\xF3 myOwn.");
5674
+ window.my = myOwn;
5675
+ await myOwn.autoSetup();
5676
+ return myOwn;
5677
+ }
5678
+
5679
+ // src/puente/pantallaDeBP.ts
5680
+ var RUTA_PANTALLA_BP = "/bp/menu";
5681
+ function rutaDePantallaBP(nombre, parametros = "") {
5682
+ const resto = parametros.replace(/^[#?]/, "").replace(/(^|&)w=[^&]*/, "").replace(/^&/, "");
5683
+ return `${RUTA_PANTALLA_BP}#w=${encodeURIComponent(nombre)}${resto ? `&${resto}` : ""}`;
5684
+ }
5685
+ function rutaDeEnlaceBP(href, baseDelDocumento, urlBackend) {
5686
+ const enlace = new URL(href, baseDelDocumento);
5687
+ const base = `${urlBackend.replace(/\/*$/, "/")}`;
5688
+ const pagina = (nombre2) => new URL(`${base}${nombre2}`, baseDelDocumento);
5689
+ if (enlace.origin !== pagina("menu").origin) return null;
5690
+ if (enlace.pathname === pagina("login").pathname) return "/login";
5691
+ if (enlace.pathname === pagina("logout").pathname) return "/logout";
5692
+ if (enlace.pathname !== pagina("menu").pathname) return null;
5693
+ const texto = (enlace.hash || enlace.search).slice(1);
5694
+ const nombre = new URLSearchParams(texto).get("w");
5695
+ return nombre ? rutaDePantallaBP(nombre, texto) : null;
5696
+ }
5697
+ function resolverPantallaBP(myOwn, nombre, contenedor) {
5698
+ const registrada = myOwn.wScreens[nombre];
5699
+ if (!registrada) return null;
5700
+ if (typeof registrada === "function") {
5701
+ return { definicion: { mainFunction: registrada }, ejecutar: registrada };
5702
+ }
5703
+ if (registrada.mainFunction) {
5704
+ return { definicion: registrada, ejecutar: registrada.mainFunction };
5705
+ }
5706
+ const { parameters, mainAction, resultClass, proceedLabel } = registrada;
5707
+ const procAux = myOwn.wScreens.procAux;
5708
+ const mostrarParametros = typeof procAux === "object" ? procAux.showParams : void 0;
5709
+ if (parameters && mostrarParametros) {
5710
+ return {
5711
+ definicion: registrada,
5712
+ ejecutar: (parametros) => mostrarParametros({ parameters, resultClass, proceedLabel }, contenedor, parametros, mainAction)
5713
+ };
5714
+ }
5715
+ return null;
5716
+ }
5717
+
5718
+ // src/puente/capturaDeNavegacion.ts
5719
+ function apiDeNavegacion() {
5720
+ const api = Reflect.get(window, "navigation");
5721
+ return typeof api === "object" && api !== null && "addEventListener" in api && typeof api.addEventListener === "function" ? api : null;
5722
+ }
5723
+ function capturarNavegacionBP(urlBackend, navegar) {
5724
+ const alHacerClick = (evento) => {
5725
+ if (evento.defaultPrevented || evento.button !== 0 || evento.ctrlKey || evento.metaKey || evento.shiftKey) return;
5726
+ const enlace = evento.target instanceof Element ? evento.target.closest("a[href]") : null;
5727
+ if (!enlace || enlace.hasAttribute("download") || (enlace.getAttribute("target") ?? "_self") !== "_self") return;
5728
+ const ruta = rutaDeEnlaceBP(enlace.getAttribute("href") ?? "", document.baseURI, urlBackend);
5729
+ if (ruta) {
5730
+ evento.preventDefault();
5731
+ evento.stopPropagation();
5732
+ navegar(ruta);
5733
+ }
5734
+ };
5735
+ document.addEventListener("click", alHacerClick, true);
5736
+ const navegacion = apiDeNavegacion();
5737
+ const alNavegar = (evento) => {
5738
+ if (!evento.cancelable || evento.destination.sameDocument || evento.downloadRequest !== null) return;
5739
+ const ruta = rutaDeEnlaceBP(evento.destination.url, document.baseURI, urlBackend);
5740
+ if (ruta) {
5741
+ evento.preventDefault();
5742
+ navegar(ruta);
5743
+ }
5744
+ };
5745
+ navegacion?.addEventListener("navigate", alNavegar);
5746
+ return () => {
5747
+ document.removeEventListener("click", alHacerClick, true);
5748
+ navegacion?.removeEventListener("navigate", alNavegar);
5749
+ };
5750
+ }
5751
+
5752
+ // src/puente/habilitacion.ts
5753
+ var habilitado = false;
5754
+ var habilitarPuenteBP = () => {
5755
+ habilitado = true;
5756
+ };
5757
+ var puenteBPHabilitado = () => habilitado;
5758
+
5759
+ // src/puente/PantallaBP.tsx
5605
5760
  var import_jsx_runtime32 = require("react/jsx-runtime");
5761
+ function buscarItemDeMenu(items, tipo) {
5762
+ for (const item of items) {
5763
+ if (item.menuType === tipo) return item;
5764
+ if (item.menuType === "menu") {
5765
+ const encontrado = buscarItemDeMenu(item.menuContent, tipo);
5766
+ if (encontrado) return encontrado;
5767
+ }
5768
+ }
5769
+ return void 0;
5770
+ }
5771
+ var PantallaBP = ({ nombre, direccion }) => {
5772
+ const navigate = (0, import_react_router_dom12.useNavigate)();
5773
+ const { clientContext } = useApp();
5774
+ const [cargando, setCargando] = (0, import_react35.useState)(true);
5775
+ const [error, setError] = (0, import_react35.useState)(null);
5776
+ const [noExiste, setNoExiste] = (0, import_react35.useState)(false);
5777
+ const contenedor = (0, import_react35.useRef)(null);
5778
+ const menu = clientContext.menu;
5779
+ (0, import_react35.useEffect)(() => {
5780
+ const elemento = contenedor.current;
5781
+ if (!elemento) return;
5782
+ let vigente = true;
5783
+ setError(null);
5784
+ setNoExiste(false);
5785
+ setCargando(true);
5786
+ const dejarDeCapturar = capturarNavegacionBP(envConfig.backendUrl, navigate);
5787
+ cargarClienteBP(envConfig.backendUrl).then((myOwn) => {
5788
+ if (!vigente) return;
5789
+ setCargando(false);
5790
+ const pantalla = resolverPantallaBP(myOwn, nombre, elemento);
5791
+ if (!pantalla) {
5792
+ setNoExiste(true);
5793
+ return;
5794
+ }
5795
+ elemento.replaceChildren();
5796
+ const item = menu ? buscarItemDeMenu(menu, nombre) : void 0;
5797
+ const desdeLaDireccion = myOwn.UriSearchToObject?.(direccion) ?? Object.fromEntries(new URLSearchParams(direccion.replace(/^[#?]/, "")).entries());
5798
+ const parametros = { ...item, ...desdeLaDireccion };
5799
+ myOwn.preDisplayPage?.(parametros, pantalla.definicion, nombre);
5800
+ pantalla.ejecutar.call(myOwn, parametros);
5801
+ }).catch((err) => {
5802
+ if (!vigente) return;
5803
+ setCargando(false);
5804
+ setError(mensajeDeError(err));
5805
+ });
5806
+ return () => {
5807
+ vigente = false;
5808
+ dejarDeCapturar();
5809
+ elemento.replaceChildren();
5810
+ };
5811
+ }, [nombre, direccion, menu, navigate]);
5812
+ if (noExiste) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(FallbackWScreen, { screenName: nombre });
5813
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_material27.Box, { children: [
5814
+ cargando && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_material27.CircularProgress, { size: 24, sx: { m: 2 } }),
5815
+ error && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_material27.Alert, { severity: "error", sx: { m: 2 }, children: error }),
5816
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { id: "main_layout", ref: contenedor })
5817
+ ] });
5818
+ };
5819
+ var PantallaDeRespaldo = () => {
5820
+ const { screenName = "" } = (0, import_react_router_dom12.useParams)();
5821
+ const { search } = (0, import_react_router_dom12.useLocation)();
5822
+ if (!puenteBPHabilitado()) {
5823
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_material27.Box, { sx: { p: 3 }, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(FallbackWScreen, { screenName }) });
5824
+ }
5825
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Navigate, { to: rutaDePantallaBP(screenName, search), replace: true });
5826
+ };
5827
+ var PaginaBP = () => {
5828
+ const { hash, search } = (0, import_react_router_dom12.useLocation)();
5829
+ const direccion = hash || search;
5830
+ const nombre = new URLSearchParams(direccion.replace(/^[#?]/, "")).get("w");
5831
+ if (!puenteBPHabilitado()) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Navigate, { to: "/", replace: true });
5832
+ if (!nombre) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_material27.Box, { sx: { p: 3 }, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_material27.Alert, { severity: "warning", children: [
5833
+ "Falta la pantalla de backend-plus (",
5834
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("code", { children: "#w=..." }),
5835
+ ")."
5836
+ ] }) });
5837
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(PantallaBP, { nombre, direccion }, nombre);
5838
+ };
5839
+
5840
+ // src/puente/GrillaBP.tsx
5841
+ var import_react36 = require("react");
5842
+ var import_react_router_dom13 = require("react-router-dom");
5843
+ var import_material28 = require("@mui/material");
5844
+ var import_jsx_runtime33 = require("react/jsx-runtime");
5845
+ var GrillaBP = ({ tabla, fixedFields }) => {
5846
+ const contenedor = (0, import_react36.useRef)(null);
5847
+ const [cargando, setCargando] = (0, import_react36.useState)(true);
5848
+ const [error, setError] = (0, import_react36.useState)(null);
5849
+ const clave = JSON.stringify(fixedFields ?? []);
5850
+ (0, import_react36.useEffect)(() => {
5851
+ const elemento = contenedor.current;
5852
+ if (!elemento) return;
5853
+ let vigente = true;
5854
+ setError(null);
5855
+ setCargando(true);
5856
+ cargarClienteBP(envConfig.backendUrl).then((myOwn) => {
5857
+ if (!vigente) return;
5858
+ setCargando(false);
5859
+ elemento.replaceChildren();
5860
+ myOwn.tableGrid(tabla, elemento, { fixedFields: JSON.parse(clave) });
5861
+ }).catch((err) => {
5862
+ if (!vigente) return;
5863
+ setCargando(false);
5864
+ setError(mensajeDeError(err));
5865
+ });
5866
+ return () => {
5867
+ vigente = false;
5868
+ elemento.replaceChildren();
5869
+ };
5870
+ }, [tabla, clave]);
5871
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_material28.Box, { children: [
5872
+ cargando && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_material28.CircularProgress, { size: 24, sx: { m: 2 } }),
5873
+ error && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_material28.Alert, { severity: "error", sx: { m: 2 }, children: error }),
5874
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: contenedor })
5875
+ ] });
5876
+ };
5877
+ var PaginaGrillaBP = () => {
5878
+ const { tableName = "" } = (0, import_react_router_dom13.useParams)();
5879
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(GrillaBP, { tabla: tableName }, tableName);
5880
+ };
5881
+
5882
+ // src/App.tsx
5883
+ var import_type_store6 = __toESM(require("type-store"), 1);
5884
+ var import_jsx_runtime34 = require("react/jsx-runtime");
5606
5885
  var LocationTracker = () => {
5607
- const location = (0, import_react_router_dom12.useLocation)();
5886
+ const location = (0, import_react_router_dom14.useLocation)();
5608
5887
  const dispatch = useAppDispatch();
5609
- (0, import_react35.useEffect)(() => {
5888
+ (0, import_react37.useEffect)(() => {
5610
5889
  dispatch(setCurrentPath(location.pathname + location.search));
5611
5890
  }, [location, dispatch]);
5612
5891
  return null;
5613
5892
  };
5614
5893
  function FrontendPlusReactRoutes({ myRoutes, myUnloggedRoutes, layout: Layout = MainLayout_default, unloggedLayout: UnloggedLayout }) {
5615
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
5616
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(InitialRedirectHandler_default, {}),
5617
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react_router_dom12.Routes, { children: [
5618
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react_router_dom12.Route, { element: UnloggedLayout ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(UnloggedLayout, {}) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Outlet, {}), children: [
5619
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/login", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(LoginPage_default, {}) }),
5620
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/logout", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(LogoutPage_default, {}) }),
5894
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
5895
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(InitialRedirectHandler_default, {}),
5896
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_router_dom14.Routes, { children: [
5897
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_router_dom14.Route, { element: UnloggedLayout ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UnloggedLayout, {}) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Outlet, {}), children: [
5898
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/login", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(LoginPage_default, {}) }),
5899
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/logout", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(LogoutPage_default, {}) }),
5621
5900
  myUnloggedRoutes
5622
5901
  ] }),
5623
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react_router_dom12.Route, { element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(PrivateRoute_default, { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Layout, {}) }), children: [
5624
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(HomePage_default, {}) }),
5625
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/home", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(HomePage_default, {}) }),
5626
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/table/:tableName", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(GenericDataGridPage_default, {}) }),
5627
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/menu/:menuName", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MenuTablePage_default, {}) }),
5628
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "/procedures/:procedureName", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ProcedureForm_default, {}) }),
5629
- Object.keys(wScreens).map((screenName) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
5630
- import_react_router_dom12.Route,
5902
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_router_dom14.Route, { element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(PrivateRoute_default, { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Layout, {}) }), children: [
5903
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(HomePage_default, {}) }),
5904
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/home", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(HomePage_default, {}) }),
5905
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/table/:tableName", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenericDataGridPage_default, {}) }),
5906
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/menu/:menuName", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MenuTablePage_default, {}) }),
5907
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/procedures/:procedureName", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ProcedureForm_default, {}) }),
5908
+ Object.keys(wScreens).map((screenName) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
5909
+ import_react_router_dom14.Route,
5631
5910
  {
5632
5911
  path: `/wScreens/${screenName}`,
5633
- element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(PantallaDeRuta, { screenName })
5912
+ element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(PantallaDeRuta, { screenName })
5634
5913
  },
5635
5914
  screenName
5636
5915
  )),
5637
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
5638
- import_react_router_dom12.Route,
5639
- {
5640
- path: "/wScreens-fallback/:screenName",
5641
- element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_material27.Box, { sx: { p: 3 }, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(FallbackWScreen, { screenName: ":screenName" }) })
5642
- }
5643
- ),
5916
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/wScreens-fallback/:screenName", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(PantallaDeRespaldo, {}) }),
5917
+ puenteBPHabilitado() && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/bp/menu", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(PaginaBP, {}) }),
5918
+ puenteBPHabilitado() && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "/bp/table/:tableName", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(PaginaGrillaBP, {}) }),
5644
5919
  myRoutes,
5645
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { style: { marginTop: "20px", marginLeft: "10px" }, children: "404 - Recurso No Encontrado" }) })
5920
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { style: { marginTop: "20px", marginLeft: "10px" }, children: "404 - Recurso No Encontrado" }) })
5646
5921
  ] }),
5647
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_router_dom12.Navigate, { to: "/", replace: true }) })
5922
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_router_dom14.Navigate, { to: "/", replace: true }) })
5648
5923
  ] })
5649
5924
  ] });
5650
5925
  }
5651
5926
  var FrontendPlusProviders = ({ children, publicPaths }) => {
5652
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_redux5.Provider, { store, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react36.PersistGate, { loading: null, persistor, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react_router_dom12.BrowserRouter, { children: [
5653
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(LocationTracker, {}),
5927
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_redux5.Provider, { store, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react38.PersistGate, { loading: null, persistor, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_router_dom14.BrowserRouter, { basename: rutaBase, children: [
5928
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(LocationTracker, {}),
5654
5929
  " ",
5655
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AppProvider, { publicPaths, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(SnackbarProvider, { children: [
5930
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(AppProvider, { publicPaths, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(SnackbarProvider, { children: [
5656
5931
  children,
5657
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(SessionExpiredMessage_default, {})
5932
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SessionExpiredMessage_default, {})
5658
5933
  ] }) })
5659
5934
  ] }) }) });
5660
5935
  };
@@ -5665,9 +5940,10 @@ var App = ({
5665
5940
  myWScreens,
5666
5941
  myClientSides,
5667
5942
  myResultsOk,
5668
- myConditions
5943
+ myConditions,
5944
+ puenteBP
5669
5945
  }) => {
5670
- (0, import_react35.useMemo)(() => {
5946
+ (0, import_react37.useMemo)(() => {
5671
5947
  if (myClientSides) {
5672
5948
  extendClientSides(myClientSides);
5673
5949
  }
@@ -5680,8 +5956,11 @@ var App = ({
5680
5956
  if (myConditions) {
5681
5957
  extendConditions(myConditions);
5682
5958
  }
5683
- }, [myClientSides, myResultsOk, myWScreens, myConditions]);
5684
- (0, import_react35.useEffect)(() => {
5959
+ if (puenteBP) {
5960
+ habilitarPuenteBP();
5961
+ }
5962
+ }, [myClientSides, myResultsOk, myWScreens, myConditions, puenteBP]);
5963
+ (0, import_react37.useEffect)(() => {
5685
5964
  import_type_store6.default.messages = import_type_store6.default.i18n.messages.es;
5686
5965
  import_type_store6.default.locale = import_type_store6.default.i18n.locale.es;
5687
5966
  const link = document.querySelector("link[rel*='icon']") || document.createElement("link");
@@ -5690,11 +5969,11 @@ var App = ({
5690
5969
  link.href = `${envConfig.backendUrl}/img/logo-128.png`;
5691
5970
  document.getElementsByTagName("head")[0].appendChild(link);
5692
5971
  }, []);
5693
- const safePaths = import_react35.default.useMemo(() => {
5972
+ const safePaths = import_react37.default.useMemo(() => {
5694
5973
  const extracted = extractPathsFromRoutes(myUnloggedRoutes);
5695
5974
  return ["/login", "/logout", ...extracted];
5696
5975
  }, [myUnloggedRoutes]);
5697
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
5976
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(FrontendPlusProviders, { publicPaths: safePaths, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
5698
5977
  FrontendPlusReactRoutes,
5699
5978
  {
5700
5979
  myRoutes,
@@ -5713,6 +5992,7 @@ var App_default = App;
5713
5992
  FrontendPlusProviders,
5714
5993
  FrontendPlusReactRoutes,
5715
5994
  GenericDataGrid,
5995
+ GrillaBP,
5716
5996
  MainLayout,
5717
5997
  SnackbarProvider,
5718
5998
  cambiarGuionesBajosPorEspacios,
@@ -5722,6 +6002,7 @@ var App_default = App;
5722
6002
  extendResultsOk,
5723
6003
  extendWScreens,
5724
6004
  extractPathsFromRoutes,
6005
+ habilitarPuenteBP,
5725
6006
  resultComponents,
5726
6007
  useApiCall,
5727
6008
  useApiCallWithoutSnackbar,