diff --git a/.github/workflows/azure-static-web-apps-blue-river-03def860f.yml b/.github/workflows/azure-static-web-apps-blue-river-03def860f.yml
index ecba24d..7477702 100644
--- a/.github/workflows/azure-static-web-apps-blue-river-03def860f.yml
+++ b/.github/workflows/azure-static-web-apps-blue-river-03def860f.yml
@@ -1,5 +1,5 @@
name: Azure Static Web Apps CI/CD
-
+
on:
push:
branches:
@@ -15,8 +15,8 @@ jobs:
runs-on: ubuntu-latest
name: Build and Deploy Job
permissions:
- id-token: write
- contents: read
+ id-token: write
+ contents: read
steps:
- uses: actions/checkout@v3
with:
@@ -28,10 +28,10 @@ jobs:
uses: actions/github-script@v6
id: idtoken
with:
- script: |
- const coredemo = require('@actions/core')
- return await coredemo.getIDToken()
- result-encoding: string
+ script: |
+ const coredemo = require('@actions/core')
+ return await coredemo.getIDToken()
+ result-encoding: string
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
diff --git a/docs/README.md b/docs/README.md
index 739a144..df38f54 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -18,6 +18,7 @@ Este índice organiza la documentación técnica del proyecto por capa arquitect
- [forms.md](forms.md) — Formatos/formularios de TI.
- [dashboard-funcionalidades.md](dashboard-funcionalidades.md) — Lógica de indicadores y disponibilidad de agentes (incl. integración con Teams).
- [loans-funcionalidades.md](loans-funcionalidades.md) — Lógica de préstamos de equipos.
+- [activos.md](activos.md) — Activos TI: catálogo, bitácora de movimientos,asignacion de usuarios, préstamo/devolución de activos y vínculo con tickets.
- [operations.md](operations.md) — Cajeros POS, información de tienda, inventario y proveedores de internet.
- [shared-funcionalidades.md](shared-funcionalidades.md) — Cliente de Power Automate (Flow) y subida de archivos a Supabase.
- [tasks-funcionalidades.md](tasks-funcionalidades.md) — Tareas y ausencias del equipo.
@@ -39,6 +40,7 @@ Este índice organiza la documentación técnica del proyecto por capa arquitect
- [components-documentar.md](components-documentar.md) — Documentación de casos, actas de entrega y escalamiento a proveedor.
- [components-formatos.md](components-formatos.md) — Formularios de seguridad de red, ERP, permisos de navegación y servicios de TI.
- [components-loans.md](components-loans.md) — Préstamo de equipos, pruebas de dispositivo, historial y devoluciones.
+- [components-activos.md](components-activos.md) — Catálogo, detalle, ficha de seguimiento, alta y asignación de activos.
- [components-tareas.md](components-tareas.md) — Tareas, ausencias, seguimiento y contador de tiempo.
- [components-usuarios-acceso.md](components-usuarios-acceso.md) — Gestión de usuarios, acceso/franquicias y login.
- [components-operativos.md](components-operativos.md) — Cajeros POS, información de tienda/proveedores, inventario y tips.
diff --git a/docs/activos.md b/docs/activos.md
new file mode 100644
index 0000000..5a38ee7
--- /dev/null
+++ b/docs/activos.md
@@ -0,0 +1,108 @@
+# Módulo Activos
+
+## Descripción general
+
+El módulo Activos cubre todo el ciclo de vida de un equipo de TI: su creación, la asignación a un usuario, el préstamo y la devolución. Cada activo tiene una ficha de seguimiento con una bitácora completa donde quedan reflejados todos sus movimientos (cambios de usuario, estado y ubicación, la fecha en que se realizaron, y los tickets por incidencias y por préstamos). Arquitectónicamente vive en `src/Funcionalidades/inventario` como una capa de hooks de React que llaman a tres repositorios propios del módulo: `ActivosTIRepository`, `MovimientoTIRepository` y `ActivoTicketRepository` (en `src/repositories/`), los tres implementados solo sobre Supabase. Para préstamos y devoluciones también usa los repositorios generales `tickets` y `logs` (Supabase) y la lista `prestamos` de SharePoint (vía `useGraphServices`), por lo que un préstamo o una devolución escribe en los dos sistemas. Los consumidores de UI son los componentes de `src/components/Inventario` (incluida la subcarpeta `Catalogo`), además de `DetallesTickets/DetallesTickets.tsx` (y `Loans/PretamosPage.tsx`, hoy comentada en el menú y en desuso); se apoyan en estos hooks para no tener lógica de negocio propia, aunque `ModalAsignarActivo.tsx` todavía conserva parte de ella (ver [components-activos.md](components-activos.md)).
+
+## Archivos
+
+| Archivo | Qué hace |
+| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `useActivosTI.ts` | Hook `useActivosTI`: lista paginada de activos con filtros por estado y ubicación, y búsqueda con espera de 300 ms (debounce); también maneja el formulario de alta/edición, con validación de campos y de serial duplicado antes de guardar. |
+| `useActivoMovimientos.ts` | Hook `useActivoMovimientos`: bitácora de movimientos de un activo (búsqueda, filtro por estado nuevo, paginación y ordenamiento por fecha, estado o responsable) y registro de un movimiento nuevo con su validación. |
+| `useActivoPrestamo.ts` | Hook `useActivoPrestamo`: ciclo de préstamo de un activo. Al iniciar lo marca `en_prestamo` y lo vincula al ticket; al devolver cierra el préstamo (SharePoint) y su ticket, devuelve el activo a su estado y usuario anteriores, deja el registro en la bitácora y avisa por correo (Power Automate). |
+| `useActivoTickets.ts` | Hook `useActivoTicket`: carga los tickets vinculados a un activo (incidencias y préstamos) desde la tabla de vínculos activo ↔ ticket. |
+| `utils/ActivosColors.ts` | Función `colorEstadoActivo`: devuelve el color de cada estado del activo (Disponible, Asignado, En préstamo) para los badges de la UI; gris si el estado no se reconoce. |
+
+## Funciones y constantes clave
+
+### Catálogo y alta de activos (`useActivosTI.ts`)
+
+- **`useActivosTI({ ActivosSvc })`**: hook que junta en un solo lugar la lista de activos y el formulario de alta/edición. Lo usan `CatalogosActivos.tsx` (solo la lista) y `ModalAltaActivo.tsx` (solo el formulario).
+- **`loadActivos(filter)` / `loadAll()`**: `loadActivos` consulta `ActivosSvc.loadActivos` y guarda `activos`, `total` y `hasNext`; si falla, deja la lista vacía y el mensaje en `error`. `loadAll()` es un atajo que llama `loadActivos` con los filtros actuales armados por `buildFilter()` (`estado`, `ubicacion_tipo`, `search`, `pageIndex`, `pageSize`, `paginated: true`).
+- **Recarga automática**: un `useEffect` vigila `criteriaKey`, que es el conjunto de filtros convertido a texto (`JSON.stringify`). Si cambia algún filtro y no se está en la página 1, primero regresa a la página 1; en cualquier otro caso llama `loadAll()`. Por eso la lista se carga sola apenas se monta el hook.
+- **Búsqueda con espera (`useDebouncedValue`)**: el texto de `search` solo se aplica 300 ms después de que el usuario deja de escribir, para no hacer una consulta por cada tecla.
+- **Paginación**: `nextPage()` solo avanza si `hasNext` es verdadero; `prevPage()` nunca baja de la página 1.
+- **`setField(key, value)` / `resetForm()` / `validateForm()`**: manejo del formulario. `validateForm` usa `validarActivoTI` (`Models/ActivoTI`) y guarda los errores por campo en `formErrors`; `resetForm` limpia el formulario, los errores y el activo seleccionado.
+- **`selectActivo(activo)`**: carga en el formulario los datos de un activo existente para editarlo.
+- **`saveActivo()`**: valida el formulario; consulta `getActivoBySerial` y, si ya existe otro activo con el mismo serial, muestra _"Ya existe un activo con este serial (código …)"_ en el campo; si no, crea el activo con estado `"Disponible"` (o lo actualiza si hay `selectedActivo`), recarga la lista y limpia el formulario.
+
+### Bitácora de movimientos (`useActivoMovimientos.ts`)
+
+- **`useActivoMovimientos({ MovimientosSvc, activoId })`**: hook de la bitácora de un activo. Lo usan `FichaActivo.tsx` (para mostrar el historial) y `ModalAsignarActivo.tsx` (para registrar un movimiento).
+- **Lista**: misma estructura que `useActivosTI` (`loadMovimientos`, `loadAll`, `buildFilter`, `criteriaKey`, recarga automática, búsqueda con espera de 300 ms y paginación). El filtro `tipoEventoFiltro` filtra por `estado_nuevo` del movimiento.
+- **`toggleSort(field, additive)`**: cambia el orden de la tabla. Los campos ordenables son `fecha_evento`, `estado_nuevo` y `responsable_nombre`; un click alterna entre ascendente y descendente, y con `additive` agrega un segundo campo de orden. Al servidor solo se envía el primero (`primarySort`).
+- **`setField` / `resetForm` / `validateForm`**: formulario del movimiento. `validateForm` usa `ValidarMovimiento` (`Models/ActivosTIMovimientos`); `resetForm` limpia todo menos `activo_id`, que se conserva para no perder el activo.
+- **`saveMovimiento()`**: valida, crea el movimiento con `createMovimientos`, recarga la bitácora y limpia el formulario. Solo registra el movimiento: actualizar el activo (estado, ubicación, usuario) lo hace el componente que lo llama.
+
+### Préstamos (`useActivoPrestamo.ts`)
+
+- **`useActivoPrestamo()`**: hook sin parámetros; toma sus dependencias de `useRepositories` (`activosTI`, `movimientosTI`, `activotickets`, `tickets`, `logs`), `useGraphServices` (`prestamos`) y `useAuth` (cuenta logueada, que queda como responsable).
+- **`iniciarPrestamoActivo(prestamo, comentario)`**: se llama después de crear el préstamo. Pasos: 1) busca el activo por `prestamo.Id_dispositivo`; 2) registra en la bitácora el cambio a `en_prestamo`, guardando el estado y usuario anteriores; 3) actualiza el activo con `estado: "en_prestamo"`, el usuario que lo recibe y `prestamo_activo_id`; 4) vincula el ticket del préstamo al activo (`createActivoTicket`). Devuelve `true` si los pasos 1 a 3 salen bien. Lo usa `ModalAsignarActivo.tsx` (también `PretamosPage.tsx`, en desuso).
+- **`finalizarPrestamoActivo(prestamo, comentario)`**: devuelve el activo a como estaba antes del préstamo. Busca el último movimiento con `estado_nuevo: "en_prestamo"` para saber a qué estado y usuario volver (si no lo encuentra, usa `"Disponible"` y sin usuario); registra el movimiento de regreso en la bitácora y actualiza el activo dejando `prestamo_activo_id: null`. Si el activo ya no está `en_prestamo`, no hace nada. Hoy se usa por dentro de `devolverPrestamoActivo`; la única llamada directa está en `PretamosPage.tsx`, en desuso.
+- **`devolverPrestamoActivo(prestamo, buenEstado, comentario)`**: devolución completa desde `FichaActivo.tsx`. Pasos: 1) cierra el préstamo en SharePoint (`Estado: "Cerrado"`, fecha de devolución y quién recibe); 2) cierra el ticket del préstamo y crea un log de tipo "Cierre" con el estado de devolución (_Buen estado_ / _Mal estado_) y el comentario; 3) llama `finalizarPrestamoActivo`; 4) envía un correo al usuario por Power Automate. El correo se envía sin esperar respuesta: si falla, la devolución igual se da por buena.
+
+### Activos vinculados a tickets (`useActivoTickets.ts`)
+
+- **`useActivoTicket({ ActivosTicketSvc })`**: hook de la tabla de vínculos activo ↔ ticket.
+- **`loadActivoTicket(filter)`**: carga los vínculos y los guarda en `activoticket`. Se usa con dos filtros distintos: `{ activo_id }` en `FichaActivo.tsx` (tickets de un activo) y `{ ticket_id }` en `DetallesTickets.tsx` (activos de un ticket).
+- **`setField` / `resetForm` / `validateForm`**: formulario para crear un vínculo (exige `activo_id` y `ticket_id`). Ningún componente lo usa: los vínculos se crean directamente con `createActivoTicket` en `Funcionalidades/Tickets/NuevoTicket.ts` (al crear un ticket con activos) y en `iniciarPrestamoActivo`.
+
+### Colores de estado (`utils/ActivosColors.ts`)
+
+- **`colorEstadoActivo(estado)`**: devuelve el color del estado a partir de `COLORES_ESTADO`; si el estado viene vacío o no existe, devuelve gris. Lo usan `CatalogosActivos`, `ModalDetalleActivo` y `FichaActivo`.
+
+### Constantes clave (resumen)
+
+| Constante | Archivo | Valor / propósito |
+| --------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `DEFAULT_PAGE_SIZE` | `useActivosTI.ts`, `useActivoMovimientos.ts` | `10`, tamaño de página inicial; **definida dos veces**, una en cada hook. |
+| `delay = 300` (de `useDebouncedValue`) | `useActivosTI.ts`, `useActivoMovimientos.ts` | Milisegundos que espera la búsqueda antes de consultar. |
+| `DEFAULT_SORTS` | `useActivoMovimientos.ts` | Orden inicial de la bitácora: `fecha_evento` descendente (lo más reciente primero). |
+| `COLORES_ESTADO` | `utils/ActivosColors.ts` | Disponible = verde, Asignado = azul, `en_prestamo` = ámbar. |
+| `ESTADOS_ACTIVO` / `EstadoActivo` | `Models/ActivosTIMovimientos.ts` | Los tres estados posibles de un activo y su etiqueta (`en_prestamo` → "En Prestamo"). |
+| `UBICACIONES_ACTIVO` / `Ubicacion_Tipo` | `Models/ActivoTI.ts` | Las tres ubicaciones posibles y su etiqueta: `bodega_central` → "Bodega", `usuario` → "Usuario", `sala_reuniones` → "Sala de reuniones". Los hooks usan el tipo `Ubicacion_Tipo` (filtro `ubicacionFiltro` y campos `ubicacion_origen`/`ubicacion_destino`); la constante la usan los componentes para los selects y etiquetas. |
+| `PRESTAMOS_FLOW_URL` | `Funcionalidades/loans/prestamos` | URL del flujo de Power Automate que envía el correo de devolución; se importa desde el módulo de préstamos. |
+
+## Flujo del módulo
+
+1. **Alta**: desde "Nuevo Activo", `saveActivo` valida el formulario, revisa que el serial no exista (`getActivoBySerial`) y crea el activo con estado `"Disponible"` en Supabase.
+2. **Listado**: `useActivosTI` alimenta el catálogo; cada cambio de filtro, búsqueda (con espera de 300 ms) o página vuelve a consultar `loadActivos` en el servidor. `colorEstadoActivo` pinta el estado de cada fila.
+3. **Asignación / reasignación**: el formulario usa `useActivoMovimientos.saveMovimiento` para dejar el movimiento en la bitácora (estado, ubicación y usuario de origen y destino, responsable y comentario). Después, el propio componente (`ModalAsignarActivo.tsx`) actualiza el activo con `updateActivo`; esta segunda parte no está en el hook.
+4. **Préstamo**: se crea primero el préstamo y su ticket con `usePrestamos` (módulo de préstamos) y luego `iniciarPrestamoActivo` marca el activo `en_prestamo`, guarda en la bitácora su estado y usuario anteriores y vincula el ticket al activo. Hoy esto se hace desde `ModalAsignarActivo.tsx` (opción "Préstamo").
+5. **Devolución**: se hace desde la ficha del activo con `devolverPrestamoActivo`, que hace todo: cierra el préstamo en SharePoint, cierra su ticket con un log de "Cierre", llama `finalizarPrestamoActivo` (el activo vuelve al estado y usuario que tenía antes del préstamo, leídos del último movimiento `en_prestamo` de la bitácora) y envía el correo.
+
+La antigua página de Préstamos (`Loans/PretamosPage.tsx`) también prestaba y devolvía activos con `iniciarPrestamoActivo` y `finalizarPrestamoActivo`, pero está comentada en el menú de `App.tsx` y su uso se irá reduciendo poco a poco; el flujo vigente es el de Inventario descrito arriba. 6. **Vínculo con tickets**: al crear un ticket con activos seleccionados, `Funcionalidades/Tickets/NuevoTicket.ts` crea los vínculos con `createActivoTicket`; los préstamos también crean el suyo en `iniciarPrestamoActivo`. `useActivoTicket.loadActivoTicket` los lee en los dos sentidos: los tickets de un activo (ficha) y los activos de un ticket (detalle del ticket). 7. **Seguimiento**: la ficha combina tres fuentes: la bitácora (`useActivoMovimientos`), las incidencias (`useActivoTicket`) y el historial de préstamos (lista `prestamos` de SharePoint).
+
+En conjunto, el **estado del activo** (`Disponible`, `Asignado`, `en_prestamo`) es lo que conecta todo el módulo: decide el color en el catálogo, si el activo se puede asignar o mover, y si se puede devolver. Y la **bitácora** es la memoria del módulo: además del historial que ve el usuario, `finalizarPrestamoActivo` la consulta para saber a qué estado regresar el activo después de un préstamo.
+
+## Dependencias
+
+**Internas:**
+
+- `src/repositories/ActivosTIRepository` (`ActivosTIRepository.ts` interfaz, `ActivosTIFromSupabase.ts`) — CRUD, listado paginado y búsqueda por serial de activos.
+- `src/repositories/MovimientoTIRepository` (`MovimientosTIRepository.ts` interfaz, `MovimientoTIFromSupabase.ts`) — bitácora de movimientos.
+- `src/repositories/ActivoTicketRepository` — vínculos activo ↔ ticket.
+- `src/repositories/TicketsRepository` y `LogRepository` — cierre del ticket del préstamo y su log, vía `useRepositories()`.
+- `src/graph/GrapServicesContext` — servicio `prestamos` (lista de SharePoint), vía `useGraphServices()`.
+- `src/auth/authContext` — cuenta logueada, que queda como responsable de cada movimiento.
+- `src/Funcionalidades/shared/FlowClient.ts` y `PRESTAMOS_FLOW_URL` (`Funcionalidades/loans/prestamos`) — envío del correo de devolución por Power Automate.
+- `src/Models/*` — `ActivoTI` (incluye `validarActivoTI` y `UBICACIONES_ACTIVO`), `ActivosTIMovimientos` (incluye `ValidarMovimiento` y `ESTADOS_ACTIVO`), `ActivoTicket`, `prestamos`, `FlujosPA`.
+- `src/utils/Date.ts` (`toISODateTimeFlex`) y `src/utils/Text.ts` (`escapeHTML`, para no inyectar HTML en el log y el correo).
+
+**Externas:**
+
+- `react` (hooks). No usa otras librerías.
+
+**Consumidores de UI** (confirmado por import en `src/components`):
+`Inventario/Catalogo/CatalogosActivos.tsx` y `Inventario/ModalAltaActivo.tsx` (`useActivosTI`), `Inventario/FichaActivo.tsx` (`useActivoMovimientos`, `useActivoTicket`, `useActivoPrestamo`), `Inventario/ModalAsignarActivo.tsx` (`useActivoMovimientos`, `useActivoPrestamo`), `DetallesTickets/DetallesTickets.tsx` (`useActivoTicket`) y `Loans/PretamosPage.tsx` (`useActivoPrestamo`; página comentada en `App.tsx`, en desuso). `colorEstadoActivo` lo usan además `CatalogosActivos`, `ModalDetalleActivo` y `FichaActivo`.
+
+## Oportunidades de mejora
+
+- **Préstamo y devolución sin "todo o nada"**: `iniciarPrestamoActivo`, `finalizarPrestamoActivo` y `devolverPrestamoActivo` hacen varios guardados seguidos (bitácora, activo, vínculo, préstamo en SharePoint, ticket, log) y si uno falla a mitad de camino los anteriores ya quedaron hechos. Ejemplos: en `devolverPrestamoActivo`, si el préstamo y el ticket ya se cerraron pero falla `finalizarPrestamoActivo`, el activo sigue `en_prestamo`; al reintentar, el ticket se vuelve a cerrar y queda un segundo log de "Cierre". En `iniciarPrestamoActivo`, si se registra el movimiento pero falla `updateActivo`, la bitácora dice "en préstamo" y el activo no. Como préstamos vive en SharePoint y el resto en Supabase, no se puede usar una sola transacción; lo más seguro es mover la parte de Supabase a una función del servidor (función SQL o Edge Function) y reintentar o marcar como pendiente lo de SharePoint.
+- **Limpieza pendiente por la página de Préstamos en desuso**: `PretamosPage.tsx` está comentada en `App.tsx`, pero el archivo sigue existiendo y sigue importando `useActivoPrestamo`. Es el único consumidor directo de `finalizarPrestamoActivo` (la ficha lo usa solo a través de `devolverPrestamoActivo`). Cuando se retire del todo, se puede borrar el archivo y dejar `finalizarPrestamoActivo` como función interna del hook. Si en algún momento se vuelve a activar, antes hay que corregirla: muestra el mensaje de éxito _"El activo vuelve a su estado anterior"_ antes de llamar `finalizarPrestamoActivo` y no revisa si funcionó, y devuelve el préstamo con la lógica de `loans` (`Terminar`, `notifyEstado`) en lugar de `devolverPrestamoActivo`.
+- **Hooks que mezclan lista y formulario**: `useActivosTI` y `useActivoMovimientos` traen la lista con su recarga automática **y** el formulario. Por eso `ModalAltaActivo.tsx` descarga el catálogo completo solo para usar el formulario, y `ModalAsignarActivo.tsx` descarga la bitácora del activo sin mostrarla. Separar cada uno en dos hooks (`useActivosList` / `useActivoForm`, por ejemplo) evita esas consultas.
+- **Código duplicado entre hooks**: `useDebouncedValue`, `DEFAULT_PAGE_SIZE` y todo el patrón de `criteriaKey` + `useEffect` de recarga + `nextPage`/`prevPage` están copiados casi igual en `useActivosTI` y `useActivoMovimientos`. Un hook genérico de lista paginada (o al menos `useDebouncedValue` en `utils`) evitaría que un arreglo en uno se olvide en el otro.
+- **Errores que solo quedan en consola**: en `useActivoPrestamo`, si falla vincular el ticket al activo o el correo de devolución, solo se escribe un `console.error` y la función devuelve `true`. El usuario no se entera de que el ticket no quedó ligado o de que la persona no recibió el correo. Además, los errores de `iniciarPrestamoActivo`/`finalizarPrestamoActivo` se reducen a `false`, sin decir qué paso falló.
+- **Código sin usar o a medias**: el formulario de `useActivoTicket` (`form`, `setField`, `validateForm`) no lo usa ningún componente, porque los vínculos se crean directo con `createActivoTicket`. En `useActivosTI` hay restos comentados de un filtro por categoría (`categoriaFiltro`) y un filtro por ubicación (`ubicacionFiltro`) que funciona pero ninguna pantalla muestra. `toggleSort` permite ordenar por varios campos, pero al servidor solo se envía el primero (`primarySort`), así que el orden múltiple no tiene efecto.
+- **Validación de serial sin protección en la base de datos**: `saveActivo` revisa el serial con `getActivoBySerial` antes de guardar y encuentra el duplicado sin importar mayúsculas o espacios al inicio y al final. El único hueco es el de dos registros al mismo tiempo: primero se consulta y después se crea, así que si dos personas registran el mismo serial a la vez, ambas pasan la revisión. La garantía real debe estar en la base de datos (índice único sobre `numero_serie` en Supabase); la consulta previa sirve para mostrar un mensaje amable. La tabla de activos no está en `supabase/migrations`, así que desde el repositorio no se puede confirmar si ese índice ya existe: conviene revisarlo en Supabase.
diff --git a/docs/components-activos.md b/docs/components-activos.md
new file mode 100644
index 0000000..1ee2678
--- /dev/null
+++ b/docs/components-activos.md
@@ -0,0 +1,102 @@
+# Componentes de Activos
+
+## Descripción general
+
+Este grupo cubre la lista principal de activos (`Catalogo/CatalogosActivos.tsx`), el detalle completo de un activo (`Catalogo/ModalDetalleActivo.tsx`), el formulario de registro individual de activos y el de asignar/reasignar usuarios, creando así un historial de movimientos en la ficha del activo (`FichaActivo.tsx`). En conjunto forman el flujo completo: crear activo → listar/buscar → ver detalle → asignar/prestar/devolver → abrir ficha del activo.
+
+## Archivos
+
+| Archivo | Qué renderiza/hace |
+| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `Catalogo/CatalogosActivos.tsx` | Tabla paginada y ordenable con filtros de texto libre (código, serial, tipo, marca o modelo) y de estado (disponible, asignado, préstamo); muestra la información más relevante de cada activo (estado, código, tipo, marca, modelo). |
+| `Catalogo/ModalDetalleActivo.tsx` | Detalle completo del activo: ubicación, usuario asignado, fecha de creación, tipo y número de serie; incluye dos botones, "Asignar usuario" y "Ver seguimiento" (`FichaActivo`), que redirigen directamente a sus páginas. |
+| `FichaActivo.tsx` | Bitácora de todos los movimientos de un activo: buscador de activos individuales, historial completo del equipo (fecha, usuarios, tickets, tickets asociados, estados, responsables, comentarios), filtros por estado y de texto libre, y botón para devolver un equipo que se encuentre en préstamo. |
+| `BuscadorActivo.tsx` | Buscador de activos compartido, listo para reutilizar; se usa en `NuevoTicketForm`, `FichaActivo` y `ModalAsignarActivo`. Limita los resultados a 10 activos (`PAGE_SIZE`) para no saturar y, en tickets, admite `isMulti` para seleccionar varios activos. |
+| `ModalAltaActivo.tsx` | Formulario completo de registro de un nuevo activo: ubicación, fecha de ingreso, código, número de serie, tipo, marca y modelo. |
+| `ModalAsignarActivo.tsx` | Formulario simplificado para asignar un activo (seleccionar usuario y ubicación), cambiar su estado o generar un préstamo (solo seleccionar usuario). Usa `BuscadorActivo` para elegir el activo o se abre desde `ModalDetalleActivo`; genera automáticamente el movimiento en `FichaActivo` y el cambio de estado en `CatalogosActivos`. |
+
+## Funciones y constantes clave
+
+### Catalogo/CatalogosActivos.tsx
+
+- Hook de Funcionalidades: `useActivosTI` (`Funcionalidades/inventario/useActivosTI`), que trae la lista de activos ya filtrada y paginada desde el servidor (`activos`, `loading`, `error`, `search`, `estadoFiltro`, `pageIndex`, `pageSize`, `total`, `hasNext`, `nextPage`, `prevPage`, `loadAll`); el repositorio `activosTI` se obtiene con `useRepositories`.
+- Estado local: `activoAsignar` (activo que se va a asignar), `activoFicha` (activo cuya bitácora se abre) y `activoDetalle` (activo que se ve en el modal de detalle); según cuál tenga valor, se renderiza `ModalAsignarActivo`, `FichaActivo` o `ModalDetalleActivo` en lugar de la tabla.
+- Handlers: `handleCerrarAsignar` (cierra el formulario de asignación sin guardar) y `handleAsignado` (cierra el formulario y llama `loadAll()` para refrescar la tabla con el nuevo estado del activo).
+- Valores calculados: `totalPages`, `rangeStart` y `rangeEnd` para mostrar "x–y de z" en el pie de la tabla.
+- Constantes UI: tamaños de página `PAGE_SIZE = [10, 20, 50, 100]`, opciones de estado `ESTADOS_ACTIVO` (`Models/ActivosTIMovimientos`) y `colorEstadoActivo` (`Funcionalidades/inventario/utils/ActivosColors`) para pintar el badge de estado.
+
+### Catalogo/ModalDetalleActivo.tsx
+
+- Es un "dumb component": recibe el activo y los callbacks por props y no consume hooks de Funcionalidades.
+- Usa `UBICACIONES_ACTIVO` y `ESTADOS_ACTIVO` para traducir los valores guardados a etiquetas legibles, `colorEstadoActivo` para el color del estado y `toISODateFlex` (`utils/Date`) para formatear la fecha de creación.
+
+### FichaActivo.tsx
+
+- Hooks de Funcionalidades: `useActivoMovimientos` (`Funcionalidades/inventario/useActivoMovimientos`) carga el historial de movimientos del activo con búsqueda, filtro por tipo de evento (`tipoEventoFiltro`), paginación y ordenamiento (`sorts`/`toggleSort`); su `loadAll` se renombra como `recargarMovimientos`. `useActivoTicket` (`Funcionalidades/inventario/useActivoTickets`) trae las incidencias (tickets asociados al activo). `useActivoPrestamo` (`Funcionalidades/inventario/useActivoPrestamo`) expone `devolverPrestamoActivo` para registrar la devolución.
+- Servicios: `useRepositories` (`movimientosTI`, `activotickets`, `activosTI`) y `useGraphServices` (servicio `prestamos` de SharePoint).
+- Estado local: `elegido` (activo escogido en el buscador cuando la ficha se abre sin activo), `activo` (activo mostrado, se actualiza tras una devolución), `prestamosActivo` (historial de préstamos del equipo) y los estados de la devolución: `devolviendo` (muestra el formulario), `buenEstado`, `comentarioDevolucion`, `guardandoDevolucion` y `errorDevolucion`.
+- Efectos: al cambiar `activoId` carga las incidencias con `loadActivoTicket`; también carga los préstamos desde Graph filtrando por `Id_dispositivo`.
+- Valores derivados: `prestamoActual` (el préstamo que coincide con `activo.prestamo_activo_id`) y `puedeDevolver` (solo si el activo está `en_prestamo` y tiene un préstamo vigente).
+- Handlers: `cancelarDevolucion` (cierra y limpia el formulario de devolución) y `confirmarDevolucion` (llama `devolverPrestamoActivo` y vuelve a consultar el activo con `getActivoById` para mostrar su nuevo estado).
+
+### BuscadorActivo.tsx
+
+- Servicios: `useRepositories` (`activosTI`); el select es `AsyncSelect` de `react-select/async`, que busca mientras el usuario escribe.
+- Función clave: `buscarActivos` (memoizada con `useCallback`) consulta `activosTI.loadActivos` con el texto escrito y devuelve las opciones; si falla, guarda el mensaje en `error` y lo muestra como "sin opciones".
+- Estado local: `error` (mensaje de error de la última búsqueda).
+- Constantes: `PAGE_SIZE = 10` para limitar los resultados y no saturar la lista.
+- Props: `SimpleProps` para seleccionar un solo activo (usado en `ModalAsignarActivo` y `FichaActivo`) y `MultiProps` (`isMulti`) para seleccionar varios (usado en `NuevoTicketForm`).
+
+### ModalAltaActivo.tsx
+
+- Hook de Funcionalidades: `useActivosTI`, del que solo usa la parte de formulario (`form`, `formErrors`, `error`, `loading`, `setField`, `saveActivo`, `resetForm`).
+- Handler: `handleSubmit` evita el envío por defecto y llama `saveActivo()`; si guarda bien, el hook deja el formulario vacío para registrar otro activo.
+- Constantes UI: `UBICACIONES_ACTIVO` para el select de ubicación.
+- El componente exportado se llama `ModalAltaActivos` (en plural), aunque el archivo está en singular.
+
+### ModalAsignarActivo.tsx
+
+- Exporta `ModalAsignarActivo`, que internamente renderiza `AsignarActivoForm` (el formulario real).
+- Hooks de Funcionalidades: `useActivoMovimientos` (`form`, `setField`, `saveMovimiento`) para registrar el movimiento en la bitácora; `useWorkers({ onlyEnabled: true })` y `useFranquicias` para las listas de usuarios destino; `usePrestamos` (`Funcionalidades/loans/prestamos`) para crear el préstamo (ticket + registro en la lista de préstamos + correo), el mismo hook que usaba la antigua página de Préstamos, hoy comentada en el menú y en desuso; y `useActivoPrestamo` (`iniciarPrestamoActivo`) para marcar el activo como prestado.
+- Servicios: `useAuth` (cuenta logueada, que queda como responsable) y `useGraphServices` (servicio de franquicias).
+- Estado local: `elegido` (activo escogido en el buscador), `errorActivo` (mensaje de error del formulario), `responsable`, `usuarioDestino`, `tipoMovimiento` (`"asignacion"` por defecto o `"prestamo"`) y `guardando`.
+- Valores derivados: `esPrestamo` (el usuario eligió préstamo), `esReasignacion` (el activo ya estaba asignado, cambia el título a "Reasignar activo") y `enPrestamo` (el activo está prestado y no se puede mover).
+- `useMemo`: `listaUsuariosDestino` une empleados y franquicias, quita duplicados por correo y los ordena alfabéticamente.
+- Efectos: al cambiar el activo precarga en el formulario la ubicación, el estado anterior, el usuario de origen e `Id_dispositivo`; al tener la cuenta logueada llena los datos del responsable.
+- Handlers: `handleSubmit` bloquea el envío si el activo está en préstamo, deriva a `registrarPrestamo` si es un préstamo y, si no, guarda el movimiento (`saveMovimiento`) y actualiza el activo (`updateActivo`) solo con lo que cambió (estado, ubicación o usuario). `registrarPrestamo` crea el préstamo, actualiza el activo y envía la notificación por correo.
+
+## Flujo del módulo
+
+1. El usuario entra desde el menú "Activos" (`App.tsx`), que tiene cuatro opciones: "Catálogo de activos" (`CatalogosActivos`), "Nuevo Activo" (`ModalAltaActivos`), "Asignación" (`ModalAsignarActivo`) y "Ficha de seguimiento" (`FichaActivo`).
+2. El registro de un nuevo activo se hace en `ModalAltaActivos`: se llena la información del equipo y `saveActivo()` lo guarda; el formulario queda limpio para otro registro y el activo aparece en el catálogo.
+3. En `CatalogosActivos` el usuario filtra por texto libre o por estado y pagina la tabla (server-side vía `useActivosTI`).
+4. Al hacer click en "Ver detalle" de un activo se llena `activoDetalle` y se abre `ModalDetalleActivo` como overlay sobre la tabla (se cierra con el botón o haciendo click afuera).
+5. Desde el detalle hay dos botones: "Asignar usuario" (`onAsignar`) llena `activoAsignar` y el catálogo renderiza `ModalAsignarActivo` en lugar de la tabla; "Ver seguimiento" (`onVerSeguimiento`) llena `activoFicha` y el catálogo renderiza `FichaActivo`.
+6. En `ModalAsignarActivo` el usuario elige el usuario destino, la ubicación o el nuevo estado, o marca el movimiento como préstamo. Al enviar, `handleSubmit`: bloquea el envío si el activo ya está en préstamo; si es préstamo, usa `registrarPrestamo` (crea el ticket y el préstamo, marca el activo como prestado y envía el correo); si no, guarda el movimiento en la bitácora (`saveMovimiento`) y actualiza el activo (`updateActivo`) con lo que cambió.
+7. Al guardar, `onSaved` llama a `handleAsignado` en `CatalogosActivos`, que cierra el formulario y ejecuta `loadAll()` para refrescar la tabla con el nuevo estado del activo.
+8. En `FichaActivo` el usuario ve el historial de movimientos, las incidencias (tickets asociados) y los préstamos del equipo. Si el activo está `en_prestamo`, puede devolverlo: `confirmarDevolucion` llama a `devolverPrestamoActivo` y vuelve a consultar el activo (`getActivoById`) para mostrar su nuevo estado.
+9. Cuando "Asignación" o "Ficha de seguimiento" se abren desde el menú (sin activo), primero muestran `BuscadorActivo` para escoger el equipo; al elegirlo se muestra el formulario o la ficha de ese activo.
+10. Desde Tickets, `NuevoTicketForm` usa `BuscadorActivo` con `isMulti` para asociar uno o varios activos al ticket; esos tickets aparecen después como incidencias en `FichaActivo`.
+
+Navegación entre componentes: props y estado local, sin rutas ni Context de UI. `CatalogosActivos` decide qué pantalla mostrar según cuál de `activoAsignar`, `activoFicha` o `activoDetalle` tenga valor, y los hijos avisan con callbacks (`onClose`, `onSaved`, `onAsignar`, `onVerSeguimiento`). `ModalAsignarActivo` usa `key={actual?.id}` para reiniciar el formulario cada vez que cambia el activo elegido. Los únicos contextos usados son de infraestructura (`RepositoriesContext`, `GraphServicesContext`, `AuthContext`).
+
+## Dependencias
+
+| Componente | Funcionalidades / Models | Librerías externas |
+| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
+| CatalogosActivos.tsx | `Funcionalidades/inventario/useActivosTI`, `Funcionalidades/inventario/utils/ActivosColors`, `Models/ActivoTI`, `Models/ActivosTIMovimientos`, `repositories/repositoriesContext` | React |
+| ModalDetalleActivo.tsx | `Funcionalidades/inventario/utils/ActivosColors`, `Models/ActivoTI`, `Models/ActivosTIMovimientos`, `utils/Date` | — |
+| FichaActivo.tsx | `Funcionalidades/inventario/useActivoMovimientos`, `Funcionalidades/inventario/useActivoTickets`, `Funcionalidades/inventario/useActivoPrestamo`, `Funcionalidades/inventario/utils/ActivosColors`, `Models/ActivoTI`, `Models/ActivosTIMovimientos`, `Models/prestamos`, `repositories/repositoriesContext`, `graph/GrapServicesContext`, `utils/Date` | `lucide-react` |
+| BuscadorActivo.tsx | `Models/ActivoTI`, `repositories/repositoriesContext` | `react-select/async` |
+| ModalAltaActivo.tsx | `Funcionalidades/inventario/useActivosTI`, `Models/ActivoTI`, `repositories/repositoriesContext` | — |
+| ModalAsignarActivo.tsx | `Funcionalidades/inventario/useActivoMovimientos`, `Funcionalidades/inventario/useActivoPrestamo`, `Funcionalidades/access/Workers`, `Funcionalidades/access/Franquicias`, `Funcionalidades/loans/prestamos`, `Models/ActivosTIMovimientos`, `Models/Commons`, `Models/Usuarios`, `repositories/repositoriesContext`, `graph/GrapServicesContext`, `auth/authContext` | `react-select` |
+| Común | `BuscadorActivo` (reutilizado en `FichaActivo`, `ModalAsignarActivo` y `NuevoTicketForm`), `colorEstadoActivo`, `ESTADOS_ACTIVO`, `UBICACIONES_ACTIVO` | — |
+
+## Oportunidades de mejora
+
+- **Decisiones tomadas con datos viejos**: `ModalAsignarActivo` decide si el activo está en préstamo (`enPrestamo`) o ya asignado (`esReasignacion`) con el objeto `activo` que se cargó al abrir la tabla, no con su estado real al momento de guardar. Si dos técnicos abren el mismo equipo al tiempo, ambos pueden asignarlo o prestarlo, y el último que guarda "gana" sin aviso. Antes de guardar se debería volver a consultar el activo (`getActivoById`) o validar el estado en la base de datos.
+- **La ficha no se refresca completa después de devolver**: `confirmarDevolucion` (`FichaActivo.tsx`) recarga el activo y los movimientos, pero no la lista de préstamos (`prestamosActivo`), que solo se carga al abrir la ficha. Después de devolver un equipo, el historial de préstamos sigue mostrando el préstamo como abierto hasta que el usuario sale y vuelve a entrar.
+- **Lógica de negocio dentro del componente**: `handleSubmit` de `ModalAsignarActivo.tsx` decide qué campos del activo cambiar (estado, ubicación, usuario, limpiarlo si queda disponible) y llama directo a `activosTI.updateActivo`. Esa regla no se puede reutilizar ni probar sin montar el formulario. Además, el objeto que se envía repite la condición `tieneDestino` dos veces, señal de que la lógica creció a base de parches. Debería vivir en `useActivoMovimientos` o en un hook nuevo de `Funcionalidades/inventario`, como ya pasa con `saveActivo` en el alta.
+- **Errores que el usuario no ve**: en `FichaActivo.tsx`, si falla la carga del historial de préstamos, y en `ModalAsignarActivo.tsx`, si falla el correo de aviso del préstamo, solo se escribe un `console.error`. El usuario ve la pantalla como si todo estuviera bien (por ejemplo, un historial de préstamos vacío) sin saber que hubo un error. Sería mejor mostrar un aviso (`react-hot-toast`, que ya se usa en tickets).
+- **Componentes grandes con muchas responsabilidades**: `FichaActivo.tsx` (≈525 líneas) mezcla buscador, historial de movimientos, incidencias, préstamos y el formulario de devolución; `ModalAsignarActivo.tsx` (≈500 líneas) mezcla asignación, reasignación, cambio de estado y préstamo. Separar la devolución en un `` y el préstamo en su propio hook haría cada archivo más fácil de leer y probar.
+- **Detalles menores de orden**: `ModalAltaActivo.tsx` y `ModalAsignarActivo.tsx` se llaman "Modal" pero son páginas completas; el archivo `ModalAltaActivo.tsx` exporta `ModalAltaActivos` (en plural); el label `" Nuevo Activo"` tiene un espacio al inicio; hay colores fijos (`#dc2626`, varios en `FichaActivo.css`) en lugar de las variables de `index.css`; y el `useEffect` que precarga el formulario de asignación no declara `setField`/`setPrestamoField` en sus dependencias.
diff --git a/docs/components-tickets.md b/docs/components-tickets.md
index d7b9c26..f282757 100644
--- a/docs/components-tickets.md
+++ b/docs/components-tickets.md
@@ -1,36 +1,39 @@
# Componentes de Tickets
## Descripción general
+
Este grupo cubre la lista principal de tickets (`Tickets.tsx`), el detalle completo de un ticket (`DetallesTickets/*`) con sus modales de acción (reasignar, recategorizar, asignar observador, cambiar fuente), el panel de mensajes/comentarios con menciones (`Modals/Messages/*`), la gestión de tickets relacionados (padre/hijo/masiva), y los formularios de creación de tickets (individual, de usuario final y masiva por Excel). En conjunto forman el flujo completo: crear ticket → listar/buscar → abrir detalle → actuar sobre el ticket → conversar con los involucrados.
## Archivos
-| Archivo | Qué renderiza/hace |
-|---|---|
-| `Tickets.tsx` | Tabla paginada y ordenable de tickets con filtros (búsqueda, estado, fuente, rango de fechas); al seleccionar una fila muestra `CaseDetail` en lugar de la tabla. |
-| `CaseAttachments.tsx` | Lista de adjuntos del ticket y visor inline (imagen, PDF/texto en iframe, video, audio) o enlace de descarga si no hay preview soportado. |
-| `CaseHeader.tsx` | Encabezado del detalle: título "Caso – ID", badge de estado y botón "Volver". |
-| `CaseInfoGrid.tsx` | Grid de datos del ticket (fechas, fuente, categoría, ANS, solicitante/observador/resolutor, título, descripción HTML); expone botones clicables para abrir modales cuando el rol lo permite. |
-| `DetallesTickets.helpers.ts` | Funciones puras: `hasRecatRole`, `getPreviewKind` (detección de tipo de archivo por extensión), `openAttachmentDownload`. |
-| `DetallesTickets.tsx` (`CaseDetail`) | Orquestador del detalle de ticket: compone header, info grid, adjuntos, tickets relacionados, seguimiento/historial, botones de acción y todos los modales. |
-| `ModalShell.tsx` | Contenedor genérico de modal (overlay + card + botón cerrar) reutilizado por los modales de acción. |
-| `Modals/ChangeFuente.tsx` (`CambiarFuente`) | Formulario para cambiar la "Fuente solicitante" del ticket (select de 4 opciones) y registrar un log de seguimiento. |
-| `Modals/Messages/CommentComposer.tsx` | Editor de comentarios basado en Tiptap con soporte de menciones `@usuario` y envío con Ctrl+Enter. |
-| `Modals/Messages/CommentText.tsx` | Renderiza el texto de un comentario resolviendo los tokens `@[id]` a "chips" con el nombre corto del usuario mencionado. |
-| `Modals/Messages/Mensajes.tsx` (`MensajesModal`) | Modal completo de comentarios del ticket: header, panel de participantes, lista de comentarios (con borrado) y composer; controla quién puede comentar. |
-| `Modals/Messages/MentionList.tsx` | Lista flotante de sugerencias de menciones agrupadas por departamento, navegable con teclado (usada por Tiptap `Suggestion`). |
-| `Modals/Messages/ParticipantPanel.tsx` (`ParticipantsPanel`) | Fila de avatares de "quienes participan en la conversación" (solicitante, resolutor, mencionados) con tooltip al pasar el mouse. |
-| `Modals/Observador.tsx` (`AsignarObservador`) | Formulario para asignar un observador al ticket, combinando opciones de empleados y franquicias en un `react-select`. |
-| `Modals/Reasignar.tsx` | Formulario para reasignar el ticket a otro resolutor, con nota obligatoria (contador de caracteres). |
-| `Modals/Recategorizar.tsx` | Formulario para cambiar Categoría/Subcategoría/Artículo del ticket vía un único `Select` de árbol combinado, y registrar el log correspondiente. |
-| `TicketsRelacionados/Relacionados.tsx` (`TicketsAsociados`) | Panel de "Tickets Asociados": muestra ticket padre y tickets hijos (con expandir/colapsar) y permite abrir el relacionador inline. |
-| `TicketsRelacionados/RelacionarTickets/Relacionador.tsx` (`RelacionadorInline`) | Formulario inline para relacionar el ticket actual como padre/hijo de otro, o cargar una relación masiva vía Excel. |
-| `NuevoTicket/NuevoTicketForm.tsx` | Formulario completo de creación de ticket (para agentes/técnicos): fuente, asunto, solicitante, resolutor (con balanceo de carga), descripción rich-text, categoría, adjuntos; incluye alternancia a modo "Masivo". |
-| `NuevoTicketUsuario/NuevoTicketFormUsuario.tsx` | Formulario simplificado de creación de ticket para usuario final (solo asunto, descripción y adjuntos). |
-| `MasiveNonFather/masiva.tsx` (`RelacionadorMasiva`) | Carga masiva de tickets desde una plantilla Excel, con descarga de plantilla y validación de tamaño de archivo. |
+
+| Archivo | Qué renderiza/hace |
+| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `Tickets.tsx` | Tabla paginada y ordenable de tickets con filtros (búsqueda, estado, fuente, rango de fechas); al seleccionar una fila muestra `CaseDetail` en lugar de la tabla. |
+| `CaseAttachments.tsx` | Lista de adjuntos del ticket y visor inline (imagen, PDF/texto en iframe, video, audio) o enlace de descarga si no hay preview soportado. |
+| `CaseHeader.tsx` | Encabezado del detalle: título "Caso – ID", badge de estado y botón "Volver". |
+| `CaseInfoGrid.tsx` | Grid de datos del ticket (fechas, fuente, categoría, ANS, solicitante/observador/resolutor, título, descripción HTML); expone botones clicables para abrir modales cuando el rol lo permite. |
+| `DetallesTickets.helpers.ts` | Funciones puras: `hasRecatRole`, `getPreviewKind` (detección de tipo de archivo por extensión), `openAttachmentDownload`. |
+| `DetallesTickets.tsx` (`CaseDetail`) | Orquestador del detalle de ticket: compone header, info grid, adjuntos, tickets relacionados, seguimiento/historial, botones de acción y todos los modales. |
+| `ModalShell.tsx` | Contenedor genérico de modal (overlay + card + botón cerrar) reutilizado por los modales de acción. |
+| `Modals/ChangeFuente.tsx` (`CambiarFuente`) | Formulario para cambiar la "Fuente solicitante" del ticket (select de 4 opciones) y registrar un log de seguimiento. |
+| `Modals/Messages/CommentComposer.tsx` | Editor de comentarios basado en Tiptap con soporte de menciones `@usuario` y envío con Ctrl+Enter. |
+| `Modals/Messages/CommentText.tsx` | Renderiza el texto de un comentario resolviendo los tokens `@[id]` a "chips" con el nombre corto del usuario mencionado. |
+| `Modals/Messages/Mensajes.tsx` (`MensajesModal`) | Modal completo de comentarios del ticket: header, panel de participantes, lista de comentarios (con borrado) y composer; controla quién puede comentar. |
+| `Modals/Messages/MentionList.tsx` | Lista flotante de sugerencias de menciones agrupadas por departamento, navegable con teclado (usada por Tiptap `Suggestion`). |
+| `Modals/Messages/ParticipantPanel.tsx` (`ParticipantsPanel`) | Fila de avatares de "quienes participan en la conversación" (solicitante, resolutor, mencionados) con tooltip al pasar el mouse. |
+| `Modals/Observador.tsx` (`AsignarObservador`) | Formulario para asignar un observador al ticket, combinando opciones de empleados y franquicias en un `react-select`. |
+| `Modals/Reasignar.tsx` | Formulario para reasignar el ticket a otro resolutor, con nota obligatoria (contador de caracteres). |
+| `Modals/Recategorizar.tsx` | Formulario para cambiar Categoría/Subcategoría/Artículo del ticket vía un único `Select` de árbol combinado, y registrar el log correspondiente. |
+| `TicketsRelacionados/Relacionados.tsx` (`TicketsAsociados`) | Panel de "Tickets Asociados": muestra ticket padre y tickets hijos (con expandir/colapsar) y permite abrir el relacionador inline. |
+| `TicketsRelacionados/RelacionarTickets/Relacionador.tsx` (`RelacionadorInline`) | Formulario inline para relacionar el ticket actual como padre/hijo de otro, o cargar una relación masiva vía Excel. |
+| `NuevoTicket/NuevoTicketForm.tsx` | Formulario completo de creación de ticket (para agentes/técnicos): fuente, asunto, solicitante, resolutor (con balanceo de carga), descripción rich-text, categoría, adjuntos; incluye alternancia a modo "Masivo". |
+| `NuevoTicketUsuario/NuevoTicketFormUsuario.tsx` | Formulario simplificado de creación de ticket para usuario final (solo asunto, descripción y adjuntos). |
+| `MasiveNonFather/masiva.tsx` (`RelacionadorMasiva`) | Carga masiva de tickets desde una plantilla Excel, con descarga de plantilla y validación de tamaño de archivo. |
## Funciones y constantes clave
### Tickets.tsx
+
- Hooks de Funcionalidades: `useUserRole` (`Funcionalidades/auth/Usuarios`), `useTickets` (`Funcionalidades/Tickets/hooks/useTickets`, que compone `useTicketsLists` + `useTicketForm` + `useTicketActions`), `calcularColorEstado` (`Funcionalidades/Tickets/utils/ticketsColors`).
- Estado local: `ticketSeleccionado` (ticket abierto en detalle).
- Efecto: al cambiar `rows` (refetch de la página), si hay un ticket seleccionado se refresca su versión remota vía `updateSelectedTicket`.
@@ -39,10 +42,12 @@ Este grupo cubre la lista principal de tickets (`Tickets.tsx`), el detalle compl
- Ordenamiento por columnas (`toggleSort`) con indicador visual y soporte de multi-orden con Shift+click.
### CaseAttachments.tsx / CaseHeader.tsx / CaseInfoGrid.tsx
+
- Son "dumb components": reciben todo por props (`rows`, `selected`, `onSelect`, `ticket`, `onVolver`, `canRecategorizar`, `onOpenCategoria/Observador/Resolutor/Fuente`) y no consumen hooks de Funcionalidades directamente.
- `CaseInfoGrid` usa el subcomponente interno `Row` y determina condicionalmente si un campo es "clicable" (botón) según `canRecategorizar`.
### DetallesTickets.tsx (`CaseDetail`)
+
- Hook de Funcionalidades: `useTicketsAttachments` (`Funcionalidades/Tickets/AttachmentsTickets`).
- Estado local: `selected` (ticket activo, puede diferir del prop tras navegar a un relacionado), `selectedAttachment`, `showSeg`, `showBotton`, `showMessages`, `activeModal` (`"recategorizar" | "reasignar" | "observador" | "fuente" | null`).
- Efectos: reset de paneles al cambiar `ticket.ID`; sincronización de `selected` con el prop `ticket`; carga de adjuntos (`attachment_type: "Creacion"`) al cambiar de ticket; selección automática del primer adjunto previsualizable cuando cambian `rows`.
@@ -50,12 +55,14 @@ Este grupo cubre la lista principal de tickets (`Tickets.tsx`), el detalle compl
- Es el punto central que decide qué modal mostrar (`activeModal`) y pasa `ticket`/`onDone` a cada uno.
### Modals/ChangeFuente.tsx, Reasignar.tsx, Recategorizar.tsx, Observador.tsx
+
- Cada uno sigue el mismo patrón: hook de `Funcionalidades/Tickets/*` (`useCambiarFuenteSolicitante`, `useReasignarTicket`, `useRecategorizarTicket`, `useAsignarObservador`) que expone `{state, errors, submitting, setField, handle...}`.
- Todos usan `react-select` con filtro insensible a acentos (`norm` de `utils/Commons`) y componente `Option` personalizado casi idéntico (duplicado) para mostrar email/jobTitle/fuente en Reasignar y Observador.
- `ChangeFuente` y `Recategorizar` registran manualmente un log de seguimiento (`logs.createLog(...)`) después de una recategorización exitosa — lógica de negocio (armar el mensaje del log) ubicada en el componente, no en el hook.
- Constantes UI: opciones fijas de "fuente" (`Aplicativo`, `Correo`, `Disponibilidad`, `Teams` en `ChangeFuente`; agrega `Presencial`, `WhatsApp` en `NuevoTicketForm`), `maxLen = 500` para la nota de reasignación.
### Modals/Messages (Mensajes, CommentComposer, CommentText, MentionList, ParticipantPanel)
+
- `MensajesModal` combina hooks de `Funcionalidades/comments/hooks/useSolviComments` (`useSolviComments`, `useCreateSolviComment`, `useDeleteSolviComment`) y de `Models/Supabase` (`useUsers`, `useCurrentUser`, `useSolviParticipants`).
- Estado derivado (no hooks): `isClosed`, `isRequester`, `isResolver`, `isParticipant`, `canComment` — reglas de permisos de comentario calculadas inline en el componente.
- `extraPeople` (useMemo): intenta emparejar solicitante/resolutor de SOLVI con usuarios de PRISMA por correo.
@@ -66,12 +73,14 @@ Este grupo cubre la lista principal de tickets (`Tickets.tsx`), el detalle compl
- `CommentText` parsea el texto con regex `/@\[(\d+)\]/g` para reemplazar ids de mención por chips con nombre.
### TicketsRelacionados/Relacionados.tsx y Relacionador.tsx
+
- `TicketsAsociados` usa `useUserRole` y `useTicketsRelacionados` (`Funcionalidades/Tickets/TicketsRelaciones`).
- Estado local: `showRel`, `loadingOpts`, `shoAll` (mostrar todos los hijos vs. los primeros 2).
- `RelacionadorInline` reutiliza el hook completo `useTickets` solo para acceder a `toTicketOptions`, `state`, `setField`, `handleCreateRelation`, `uploadMasiva` — trae mucho más de lo que necesita.
- Estado local: `mode` (`"padre" | "hijo" | "masiva"`), `tickets` (opciones cargadas de forma asíncrona en un efecto con guarda `alive`).
### NuevoTicketForm.tsx / NuevoTicketFormUsuario.tsx / masiva.tsx
+
- `NuevoTicketForm` combina `useNuevoTicketForm`, `useFranquicias`, `useWorkers`, `useUsuarios`, `useUserRole` y arma tres listas combinadas: `combinedOptions` (empleados+franquicias), `treeOptions` (categoría>subcategoría>artículo) y `opcionesFuentes` (constante embebida con 5 fuentes).
- Estado local: `masiva` (alterna a `RelacionadorMasiva`), `categoriasProps` (ids numéricos de categoría/sub/artículo, paralelos a los títulos guardados en `state`).
- Handler de resolutor con lógica de negocio embebida: si `opt.jobTitle === "Tecnico"`, llama `balanceCharge` y bloquea la selección con un `toast.error` si el resolutor tiene demasiados casos — excepto un correo hardcodeado (`mamartinez@estudiodemoda.com.co`) que se salta la validación. Contiene además un `console.table(opt)` de depuración.
@@ -79,6 +88,7 @@ Este grupo cubre la lista principal de tickets (`Tickets.tsx`), el detalle compl
- `RelacionadorMasiva` reutiliza `useTickets` completo solo por `state`, `setField`, `uploadMasiva`; genera la descarga de la plantilla creando un `` temporal.
## Flujo del módulo
+
1. El usuario entra a `Tickets.tsx`, filtra/busca/pagina la tabla de tickets (server-side vía `useTicketsLists`).
2. Al hacer click (o Enter/Espacio) en una fila, `ticketSeleccionado` se setea y se renderiza `CaseDetail` (`DetallesTickets.tsx`) en lugar de la tabla.
3. `CaseDetail` muestra `CaseHeader` + `CaseInfoGrid` (con datos y botones condicionados por rol) + `CaseAttachments` + `TicketsAsociados`.
@@ -93,23 +103,24 @@ Navegación entre componentes: mayormente props drilling explícito (`ticket`, `
## Dependencias
-| Componente | Funcionalidades / Models | Librerías externas |
-|---|---|---|
-| Tickets.tsx | `Funcionalidades/auth/Usuarios` (useUserRole), `Funcionalidades/Tickets/hooks/useTickets`, `Funcionalidades/Tickets/utils/ticketsColors`, `Models/Tickets`, `auth/authContext`, `graph/GrapServicesContext`, `repositories/repositoriesContext` | React |
-| CaseAttachments/Header/InfoGrid | `Models/Tickets`, `Funcionalidades/Tickets/AttachmentsTickets` (tipo) | — |
-| DetallesTickets.tsx | `Funcionalidades/Tickets/AttachmentsTickets` (useTicketsAttachments) | — |
-| ChangeFuente | `Funcionalidades/Tickets/CambiarFuente`, `repositories/repositoriesContext`, `auth/authContext` | `react-select` |
-| Reasignar | `Funcionalidades/Tickets/Reasignar`, `Funcionalidades/auth/Usuarios` (useUsuarios), `graph/GrapServicesContext` | `react-select` |
-| Recategorizar | `Funcionalidades/Tickets/Recategorizar`, `auth/authContext` | `react-select` |
-| Observador | `Funcionalidades/Tickets/Observador`, `Funcionalidades/access/Workers`, `Funcionalidades/access/Franquicias` | `react-select` |
-| Mensajes.tsx / CommentComposer / MentionList / ParticipantPanel | `Funcionalidades/comments/hooks/useSolviComments`, `Models/Supabase/useUsers`, `Models/Supabase/useCurrentUser`, `Models/Supabase/useSolviParticipants`, `repositories/ParticipantsRepository/MessagesRepository`, `utils/mentions` | `@tiptap/react`, `@tiptap/extension-mention`, `@tiptap/suggestion`, `@tiptap/starter-kit`, `@tiptap/extensions`, `lucide-react` |
-| Relacionados.tsx / Relacionador.tsx | `Funcionalidades/Tickets/TicketsRelaciones`, `Funcionalidades/Tickets/hooks/useTickets`, `Funcionalidades/auth/Usuarios` | `react-select` |
-| NuevoTicketForm.tsx | `Funcionalidades/Tickets/NuevoTicket`, `Funcionalidades/access/Franquicias`, `Funcionalidades/access/Workers`, `Funcionalidades/auth/Usuarios` | `react-select`, `react-hot-toast` |
-| NuevoTicketFormUsuario.tsx | `Funcionalidades/Tickets/NuevoTicket` (useNuevoUsuarioTicketForm) | — |
-| MasiveNonFather/masiva.tsx | `Funcionalidades/Tickets/hooks/useTickets` | — |
-| Común a formularios | `RichTextBase64` (componente propio, editor rich text con imágenes base64), `Trunc` (`components/Trunc/trunc`), `HtmlContent` (`components/Renderizador/Renderizador`) | — |
+| Componente | Funcionalidades / Models | Librerías externas |
+| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
+| Tickets.tsx | `Funcionalidades/auth/Usuarios` (useUserRole), `Funcionalidades/Tickets/hooks/useTickets`, `Funcionalidades/Tickets/utils/ticketsColors`, `Models/Tickets`, `auth/authContext`, `graph/GrapServicesContext`, `repositories/repositoriesContext` | React |
+| CaseAttachments/Header/InfoGrid | `Models/Tickets`, `Funcionalidades/Tickets/AttachmentsTickets` (tipo) | — |
+| DetallesTickets.tsx | `Funcionalidades/Tickets/AttachmentsTickets` (useTicketsAttachments) | — |
+| ChangeFuente | `Funcionalidades/Tickets/CambiarFuente`, `repositories/repositoriesContext`, `auth/authContext` | `react-select` |
+| Reasignar | `Funcionalidades/Tickets/Reasignar`, `Funcionalidades/auth/Usuarios` (useUsuarios), `graph/GrapServicesContext` | `react-select` |
+| Recategorizar | `Funcionalidades/Tickets/Recategorizar`, `auth/authContext` | `react-select` |
+| Observador | `Funcionalidades/Tickets/Observador`, `Funcionalidades/access/Workers`, `Funcionalidades/access/Franquicias` | `react-select` |
+| Mensajes.tsx / CommentComposer / MentionList / ParticipantPanel | `Funcionalidades/comments/hooks/useSolviComments`, `Models/Supabase/useUsers`, `Models/Supabase/useCurrentUser`, `Models/Supabase/useSolviParticipants`, `repositories/ParticipantsRepository/MessagesRepository`, `utils/mentions` | `@tiptap/react`, `@tiptap/extension-mention`, `@tiptap/suggestion`, `@tiptap/starter-kit`, `@tiptap/extensions`, `lucide-react` |
+| Relacionados.tsx / Relacionador.tsx | `Funcionalidades/Tickets/TicketsRelaciones`, `Funcionalidades/Tickets/hooks/useTickets`, `Funcionalidades/auth/Usuarios` | `react-select` |
+| NuevoTicketForm.tsx | `Funcionalidades/Tickets/NuevoTicket`, `Funcionalidades/access/Franquicias`, `Funcionalidades/access/Workers`, `Funcionalidades/auth/Usuarios` | `react-select`, `react-hot-toast` |
+| NuevoTicketFormUsuario.tsx | `Funcionalidades/Tickets/NuevoTicket` (useNuevoUsuarioTicketForm) | — |
+| MasiveNonFather/masiva.tsx | `Funcionalidades/Tickets/hooks/useTickets` | — |
+| Común a formularios | `RichTextBase64` (componente propio, editor rich text con imágenes base64), `Trunc` (`components/Trunc/trunc`), `HtmlContent` (`components/Renderizador/Renderizador`) | — |
## Oportunidades de mejora
+
- **Residuo de refactor/copy-paste en rutas y comentarios**: `CommentComposer.tsx`, `CommentText.tsx`, `MentionList.tsx` y `ParticipantPanel.tsx` (en `src/components/DetallesTickets/Modals/Messages/`) empiezan con comentarios de cabecera `// src/features/requests/components/mentions/...`, indicando que fueron copiados de otra estructura de proyecto sin actualizar. Puede confundir a quien navegue el código.
- **Debug leftovers en producción**: `Mensajes.tsx` línea ~157 tiene `useEffect(() => console.log(comments), [comments])`; `NuevoTicketForm.tsx` línea ~216 tiene `console.table(opt)` dentro del `onChange` del resolutor. Ambos deberían eliminarse.
- **Lógica de negocio filtrada en componentes**: `ChangeFuente.tsx` y `Recategorizar.tsx` arman manualmente el texto del log de seguimiento (`"El resolutor cambió la fuente solicitante a: " + fuente`) y llaman `logs.createLog(...)` directamente desde el componente en vez de que esto viva dentro de `useCambiarFuenteSolicitante`/`useRecategorizarTicket`. La regla de "correo exento de balanceo de carga" (`mamartinez@estudiodemoda.com.co`) en `NuevoTicketForm.tsx` está hardcodeada en JSX, no en configuración ni en el hook `balanceCharge`.
diff --git a/package-lock.json b/package-lock.json
index d00f898..c7b3840 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,9 +18,11 @@
"@tiptap/react": "^3.29.2",
"@tiptap/starter-kit": "^3.29.2",
"@tiptap/suggestion": "^3.29.2",
+ "change-case": "^5.4.4",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"dompurify": "^3.2.7",
+ "lodash-es": "^4.18.1",
"lucide-react": "^1.28.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
@@ -3065,6 +3067,12 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/change-case": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
+ "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
+ "license": "MIT"
+ },
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
@@ -4008,6 +4016,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
diff --git a/package.json b/package.json
index 68a091d..1dac967 100644
--- a/package.json
+++ b/package.json
@@ -20,9 +20,11 @@
"@tiptap/react": "^3.29.2",
"@tiptap/starter-kit": "^3.29.2",
"@tiptap/suggestion": "^3.29.2",
+ "change-case": "^5.4.4",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"dompurify": "^3.2.7",
+ "lodash-es": "^4.18.1",
"lucide-react": "^1.28.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
diff --git a/public/Plantilla Masiva.xlsx b/public/Plantilla Masiva.xlsx
index 73fd1a9..2810bb0 100644
Binary files a/public/Plantilla Masiva.xlsx and b/public/Plantilla Masiva.xlsx differ
diff --git a/src/App.css b/src/App.css
index b0a0134..39fbe79 100644
--- a/src/App.css
+++ b/src/App.css
@@ -147,7 +147,9 @@ html, body{
.sideItem:hover{ background:var(--sb-hover); }
.sideItem:active{ transform:translateY(1px); }
.sideItem--active{ background:var(--sb-pill); outline:2px solid rgba(255,255,255,.08); }
-.sideItem__icon{ width:22px; display:inline-flex; justify-content:center; opacity:.94; }
+.sideItem__icon,
+.sb-icon-wrap{ width:22px; flex-shrink:0; display:inline-flex; justify-content:center; opacity:.94; }
+.sb-icon{ width:22px; height:22px; object-fit:contain; }
.sideItem__label{ font-size:15px; }
.caret{ margin-right:6px; opacity:.85; transition:transform .2s ease; }
.caret.rot{ transform:rotate(90deg); }
diff --git a/src/App.tsx b/src/App.tsx
index b1a3ce0..8f684f0 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,10 +10,16 @@ import CrearPlantilla from "./components/NuevaPlantilla/NuevaPlantilla";
import UsuariosPanel from "./components/Usuarios/Usuarios";
import AnsAdmin from "./components/Admin/AnsAdmin/AnsAdmin";
import CajerosPOSForm from "./components/CajerosPOS/CajerosPOS";
+
+import CatalogosActivos from "./components/Inventario/Catalogo/CatalogosActivos";
+
import type { User } from "./Models/User";
import { useAuth } from "./auth/authContext";
import { useUserRole } from "./Funcionalidades/auth/Usuarios";
-import { GraphServicesProvider, useGraphServices } from "./graph/GrapServicesContext";
+import {
+ GraphServicesProvider,
+ useGraphServices,
+} from "./graph/GrapServicesContext";
import type { UsuariosSPService } from "./Services/Usuarios.Service";
import HomeIcon from "./assets/home.svg";
import addIcon from "./assets/add.svg";
@@ -25,30 +31,45 @@ import settingsIcon from "./assets/settings.svg";
import templateIcon from "./assets/template.svg";
import WelcomeSolvi from "./components/Welcome/Welcome";
import DashBoardPage from "./components/Dashboard/DashboardPage";
-import usersIcon from "./assets/users.svg"
-import ActionsIcon from "./assets/actions.svg"
-import siesaIcon from "./assets/siesa.png"
-import storageIcon from "./assets/storage.svg"
-import cajerosIcon from "./assets/cajeros.svg"
+import usersIcon from "./assets/users.svg";
+import ActionsIcon from "./assets/actions.svg";
+import siesaIcon from "./assets/siesa.png";
+import storageIcon from "./assets/storage.svg";
+import cajerosIcon from "./assets/cajeros.svg";
import { logout } from "./auth/msal";
import AnnouncementsTable from "./components/TipsTable/TipsTable";
import { useTheme } from "./Funcionalidades/auth/Theme";
import TeamsEventForm from "./components/Ausencia/Ausencia";
import { StoragePage } from "./components/Storage/StoragePage";
-import { PrestamosPage } from "./components/Loans/PretamosPage";
-import loanImage from "./assets/device.svg"
+// import { PrestamosPage } from "./components/Loans/PretamosPage";
+
+import loanImage from "./assets/device.svg";
import ReportsPage from "./components/Reports/ReportsPage";
import { useRepositories } from "./repositories/repositoriesContext";
import type { TicketsRepository } from "./repositories/TicketsRepository/TicketRepository";
import type { LogRepository } from "./repositories/LogRepository/LogRespository";
import { useContador } from "./Funcionalidades/timeCounter/hooks/useCounter";
-/* ============================================================
- Tipos de navegación y contexto de visibilidad
- ============================================================ */
+import FichaActivo from "./components/Inventario/FichaActivo";
+import { ModalAltaActivos } from "./components/Inventario/ModalAltaActivo";
+import { ModalAsignarActivo } from "./components/Inventario/ModalAsignarActivo";
-type RenderCtx = { services?: { Tickets: TicketsRepository; Usuarios: UsuariosSPService; Logs: LogRepository } };
+/* ============================================================
+ Tipos de navegación y contexto de visibilidad
+ ============================================================ */
+
+type RenderCtx = {
+ services?: {
+ Tickets: TicketsRepository;
+ Usuarios: UsuariosSPService;
+ Logs: LogRepository;
+ };
+};
-type Services = { Tickets: TicketsRepository; Usuarios: UsuariosSPService; Logs: LogRepository };
+type Services = {
+ Tickets: TicketsRepository;
+ Usuarios: UsuariosSPService;
+ Logs: LogRepository;
+};
export type MenuItem = {
id: string;
@@ -68,32 +89,186 @@ export type NavContext = {
flags?: Set;
hasService?: (k: keyof Services) => boolean;
};
-
+
/* ============================================================
- Árbol único de navegación con reglas de visibilidad
- ============================================================ */
+ Árbol único de navegación con reglas de visibilidad
+ ============================================================ */
const NAV: MenuItem[] = [
- { id: "home", label: "Home", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"], autocollapse: true },
- { id: "ticketform", label: "Nuevo Ticket", icon: , to: () => , roles: ["Administrador", "Tecnico", "Listo"] },
- { id: "ticketform_user", label: "Nuevo Ticket", icon: , to: , roles: ["Usuario", "Jefe de zona", "Listo"] },
- { id: "ticketTable", label: "Ver Tickets", icon: , to: , autocollapse: true },
- { id: "task", label: "Tareas", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"], autocollapse: true },
- { id: "formatos", label: "Formatos", icon: , to: , roles: ["Administrador", "Listo"] },
- { id: "info", label: "Información", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] },
- { id: "storage", label: "Almacenamiento", icon: , to: , roles: ["Administrador", "Listo"] },
- { id: "loan", label: "Prestamos", icon: , to: , roles: ["Administrador", "Listo", "Tecnico"] },
- { id: "admin", label: "Administración", icon: , roles: ["Administrador", "Tecnico", "Listo"], children: [
- { id: "plantillas", label: "Plantillas", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] },
- { id: "usuarios", label: "Usuarios", icon: , to: , roles: ["Administrador"] },
- { id: "ans", label: "ANS", icon: , to: , roles: ["Administrador"] },
- { id: "tips", label: "Tips", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] },
- { id: "reports", label: "Reportes", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] },
+ {
+ id: "home",
+ label: "Home",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ autocollapse: true,
+ },
+ {
+ id: "ticketform",
+ label: "Nuevo Ticket",
+ icon: ,
+ to: () => ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ },
+ {
+ id: "ticketform_user",
+ label: "Nuevo Ticket",
+ icon: ,
+ to: ,
+ roles: ["Usuario", "Jefe de zona", "Listo"],
+ },
+ {
+ id: "ticketTable",
+ label: "Ver Tickets",
+ icon: ,
+ to: ,
+ autocollapse: true,
+ },
+ {
+ id: "task",
+ label: "Tareas",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ autocollapse: true,
+ },
+ {
+ id: "act",
+ label: "Activos",
+ icon: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ children: [
+ {
+ id: "catalogoA",
+ label: "Catalogo de activos",
+ icon: ,
+ to: () => ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ },
+ {
+ id: "Activos",
+ label: " Nuevo Activo",
+ icon: ,
+ to: () => ,
+ roles: ["Administrador", "Técnico", "Listo"],
+ },
+ {
+ id: "Asignacion",
+ label: "Asignación",
+ icon: ,
+ to: () => ,
+ roles: ["Administrador", "Técnico", "Listo"],
+ },
+ {
+ id: "FichaA",
+ label: "Ficha de seguimiento",
+ icon: ,
+ to: () => ,
+ roles: ["Administrador", "Técnico", "Listo"],
+ },
+ ],
+ },
+ {
+ id: "formatos",
+ label: "Formatos",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Listo"],
+ },
+ {
+ id: "info",
+ label: "Información",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ },
+ {
+ id: "storage",
+ label: "Almacenamiento",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Listo"],
+ },
+
+ // {
+ // id: "loan",
+ // label: "Prestamos",
+ // icon: ,
+ // to: ,
+ // roles: ["Administrador", "Listo", "Tecnico"],
+ // },
+ {
+ id: "admin",
+ label: "Administración",
+ icon: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ children: [
+ {
+ id: "plantillas",
+ label: "Plantillas",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ },
+ {
+ id: "usuarios",
+ label: "Usuarios",
+ icon: ,
+ to: ,
+ roles: ["Administrador"],
+ },
+ {
+ id: "ans",
+ label: "ANS",
+ icon: ,
+ to: ,
+ roles: ["Administrador"],
+ },
+ {
+ id: "tips",
+ label: "Tips",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ },
+ {
+ id: "reports",
+ label: "Reportes",
+ icon: ,
+ to: ,
+ roles: ["Administrador", "Tecnico", "Listo"],
+ },
],
},
- {id: "acciones", label: "Acciones", icon: , roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], children: [
- {id: "siesa", label: "Siesa", roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], icon: , children: [
- {id: "cajpos", label: "Cajeros POS", icon: , to: (rctx: RenderCtx) => rctx.services ? :