# Informe de cambios — Multi-sucursal por usuario
**Fecha:** 2026-09-08
**Tema:** Permitir que un usuario del sistema opere sobre varias sucursales.

---

## 1. Resumen

Antes, cada usuario de `cat_usuario_sistema` se vinculaba a **un solo empleado**
(`cat_usuario_sistema.id_empleado`), y ese empleado pertenece a **una sola sucursal**
(`cat_empleado.id_sucursal`). Esa relación "1 usuario = 1 sucursal" estaba incrustada en
la lógica de permisos de todo el sistema vía `$_SESSION["sucursal_sistema"]` (que en
realidad guarda el `id_empleado`, no un id de sucursal).

Ahora un usuario puede vincularse a **varios empleados** (tabla nueva
`cat_usuario_empleado`), y cada empleado le aporta su sucursal. En las pantallas con
selector de sucursal:

- **Con sucursales asignadas** → el selector muestra solo esas.
- **Sin sucursales asignadas** → el selector muestra todas (compatibilidad total con
  usuarios "clásicos" y administradores).

No se modificó `$_SESSION["sucursal_sistema"]` ni `vendor/Security.php` (ni sus copias
en `tablero/`, `tablerov2/`, `servicios/`).

---

## 2. Base de datos

### Script: `SQL_MULTI_SUCURSAL_USUARIO.sql` (nuevo)

```sql
CREATE TABLE IF NOT EXISTS cat_usuario_empleado (
  id          INT AUTO_INCREMENT PRIMARY KEY,
  id_usuario  INT NOT NULL,
  id_empleado INT NOT NULL,
  UNIQUE KEY uq_usuario_empleado (id_usuario, id_empleado),
  KEY idx_usuario (id_usuario),
  KEY idx_empleado (id_empleado)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT IGNORE INTO cat_usuario_empleado (id_usuario, id_empleado)
SELECT id, id_empleado
FROM cat_usuario_sistema
WHERE id_empleado IS NOT NULL AND id_empleado > 0;
```

### Estado de ejecución

| Entorno     | Tabla creada | Migración | Notas |
|-------------|:---:|:---:|---|
| Producción  | ✅ | ✅ 37 filas | 37 usuarios que tenían `id_empleado` → 1 fila c/u. Verificado. |
| Local       | ❌ | ❌ | Pendiente: correr el `.sql` en phpMyAdmin local. |

`cat_usuario_sistema.id_empleado` **se conserva** y pasa a significar "empleado activo".
Como `login.php` ya lo pasa a `Security::Loguearse()`, sirve como "última sucursal
elegida" sin columna nueva.

---

## 3. Pantalla de usuarios (asignación)

### `usuarios/index.php`
- Modal "Vincular": el `<select #usuarioEle>` pasó de selección única a `multiple`.
  Cada opción muestra "Empleado — Sucursal".
- Se quitó la opción "SIN ASIGNAR".
- Cache-buster del JS: `index.js?v=1` → `index.js?v=2`.

### `usuarios/index.js`
- `vincular()`: al abrir el modal hace AJAX a `op=empleados_usuario` y pre-selecciona
  los empleados ya vinculados.
- `guardarvinculo1()`: envía `empleados` (array) en lugar de `usuario` (valor único).

### `usuarios/acciones.php`
- **`op=actualizar_empleado`** (reescrito): recibe `empleados[]`, hace `DELETE` + `INSERT`
  en `cat_usuario_empleado`, y recalcula `cat_usuario_sistema.id_empleado`:
  - lista vacía → `0` (usuario "ve todo")
  - si el activo actual sigue en la lista → se conserva
  - si no → el primero de la lista
  - si cambia y es el usuario logueado → actualiza `$_SESSION['sucursal_sistema']`
- **`op=empleados_usuario`** (nuevo): devuelve los `id_empleado` vinculados a un usuario.
- **`op=datos`**: la columna "Sucursal" del listado ahora muestra todas las sucursales
  del usuario (`GROUP_CONCAT` sobre `cat_usuario_empleado`).

---

## 4. Helper compartido

### `vendor/funcionesSucursal.php` (nuevo)

```php
sucursalesUsuarioLogueado($conexion)      // int[]  — ids de sucursal asignadas ([] = todas)
sqlFiltroSucursalUsuario($conexion, $col) // " AND <col> IN (...) "  o  ""
```

Regla: si el usuario tiene sucursales asignadas se filtra por ellas; si no, no se
restringe (se ven todas).

---

## 5. Pantallas ajustadas (selector de sucursal)

### 5.1 Inline (bloque propio, hechas antes del helper)

| Archivo | Cambio |
|---|---|
| `inventarioSucursal/inventarioMulti.php` | Dropdown `#sucursal`: todas las asignadas (antes: solo la del empleado activo). Selección por defecto = sucursal del empleado activo. |
| `inventarioSucursal/seccionesInventario.php` | Dropdown `#sucursal` (con `replicacion = 1`): solo asignadas, o todas. |
| `sucursales/inventarioSucursal.php` | Dropdown `#sucursal` (value = `clave`): solo asignadas, o todas. |
| `gastosFijos/estadoDeResultados.php` | Dropdown `#sucursal`: solo asignadas, o todas. |
| `gastosFijos/reporteSemanal4.php` | Idem (value = `clave`). |
| `gastosFijos/reporteSemanal5.php` | Idem, dropdown `#sucursal`. |
| `gastosFijos/reporteSemanal7.php` | Idem. El `SELECT ... FROM cat_sucursal` del *seed* de `gastosfijos_ideales_sucursal` se dejó intacto. |
| `reportes/reporteComprasSuc.php` | Dropdown `#sucursal` (con `replicacion = '1'`): solo asignadas, o todas. |
| `reportes/reporteCancelaciones.php` | Dropdown `#sucursal`: solo asignadas, o todas. |
| `reportes/reporteChecadas.php` | Dropdown `#sucursal`: solo asignadas, o todas. Referencias cosméticas a `sucursal_sistema` (CSS, breadcrumb, ocultar columna) sin cambios. |
| `pedidos/acciones.php` (`op=datos`) | El listado de pedidos filtra `id_sucursal IN (asignadas)`; sin asignadas o admin → todos. |

### 5.2 Con el helper `funcionesSucursal.php` (lote `/reportes/`)

Patrón aplicado:
`$sqlDetalle = "SELECT ... FROM cat_sucursal WHERE 1=1 " . sqlFiltroSucursalUsuario($conexion, 'id');`

| Archivo | Nota |
|---|---|
| `reportes/dashboard.php` | |
| `reportes/historicosPedidos.php` | |
| `reportes/reporteKpi.php` | |
| `reportes/ventaContadora.php` | |
| `reportes/VentasComisariatoPorSucursal.php` | |
| `reportes/reporteCortesias.php` | |
| `reportes/reporteKar.php` | |
| `reportes/reporteSucursal.php` | |
| `reportes/tiempoReparto.php` | |
| `reportes/ingredientesConsumidos.php` | |
| `reportes/reporteInventario.php` | |
| `reportes/reporte_superClase.php` | `<select multiple>` |
| `reportes/inventarioSucursal.php` | conserva `where replicacion = 1` |
| `reportes/compraSucGastos.php` | 2 dropdowns (`#sucursal`, `#sucursalgasto`) |
| `reportes/reporteSucursalgastos.php` | 2 dropdowns |
| `reportes/ventasEnSucursal.php` | 2 dropdowns |
| `reportes/proyeccion.php` | Reemplaza el filtro `sucursal_sistema` por el helper (conserva `replicacion = '1'`) |
| `reportes/proyecciones.php` | Idem |
| `reportes/reporteSucursalconsolidado.php` | Idem |
| `reportes/para_inventarios.php` | conserva `WHERE modelo_neg = 1 ... ORDER BY nombre_sucursal` |

### 5.3 No aplica

- `reportes/comprasproveedor.php` — su `id='sucursal'` es en realidad un selector de
  proveedores (`cat_proveedor`).

---

## 6. Menú lateral

### `estructura/menu.php`
El grupo "Sucursales" solo aparecía con permiso a los módulos 18, 19 o 20. Se agregó el
módulo **58** ("Inventarios" / *Catalogos de Sucursales*) a la condición, para que un
usuario con solo el 58 vea el sub-link "Inventario sucursales".

```php
if (PermisoModulo(18) || PermisoModulo(19) || PermisoModulo(20) || PermisoModulo(58))
  $sucursales_menu = true;
```

---

## 7. Estado de despliegue

| Bloque | Subido a producción |
|---|:---:|
| `SQL_MULTI_SUCURSAL_USUARIO.sql` + ejecución en BD | ✅ |
| `usuarios/*` (index.php, index.js, acciones.php) | ✅ |
| `inventarioSucursal/inventarioMulti.php`, `seccionesInventario.php` | ✅ |
| `sucursales/inventarioSucursal.php` | ✅ |
| `gastosFijos/estadoDeResultados.php`, `reporteSemanal4/5/7.php` | ✅ |
| `reportes/reporteComprasSuc.php`, `reporteCancelaciones.php`, `reporteChecadas.php` | ✅ |
| `pedidos/acciones.php` | ✅ |
| `estructura/menu.php` | ✅ |
| `vendor/funcionesSucursal.php` + lote de 18 reportes (sección 5.2) | ⏳ pendiente de `sube` |

---

## 8. Pendientes / siguiente fase

- **Switcher de sucursal activa** en `estructura/menu.php` + endpoint
  `estructura/cambiar_sucursal.php` (para que el usuario cambie entre sus sucursales
  sin volver a iniciar sesión). Aún no implementado.
- Correr el `.sql` en la **BD local**.
- Revisar `catalogosAsucursales/*` y otros módulos con el patrón
  `id_sucursal = (select id_sucursal from cat_empleado where id = sucursal_sistema)`
  que aún no se han tocado.
- Endpoints de **escritura** (inserción de pedidos, etc.): usan el empleado activo →
  insertan en la sucursal activa. Correcto mientras solo haya una activa a la vez.
