# Correcciones de seguridad — Huésped Manager (Reserva del Mar II)

Versión 5: incluye C1–C7, A1–A7, M1–M6, B1–B3 y el nuevo diseño "Recepción".

## Antes de subir (obligatorio, en este orden)

1. **Revocar credenciales expuestas** (estaban en el código):
   - SendGrid: revocar las 4 claves (`SG.VkOO…`, `SG.ErEr…`, `SG.TFHc…`, `SG.xfgc…`) y crear una nueva con permiso solo "Mail Send".
   - Base de datos: cambiar la contraseña de el usuario de la base de datos.
   - SMTP: cambiar las contraseñas de `morrosioconfirmacion@` y `spiaggia-confirmacion@huespedmanager.com`.
2. **Configuración fuera de public_html:** copiar `hm_config/entidad.php.ejemplo` a `/home/<usuario_hosting>/hm_config/entidad.php`, completar con las credenciales nuevas, `chmod 600`.
3. **Base de datos:** ejecutar `sql/c7_password.sql` en phpMyAdmin (amplía la columna `password` y crea `login_intentos`). Puede hacerse antes o después de subir el código: mientras la columna no se amplíe, el sistema sigue guardando MD5 para no dañar cuentas.
4. Respaldar la carpeta actual y subir la carpeta `sistema/` corregida.
5. Pedir a todos los usuarios que **cierren sesión y vuelvan a entrar** (las sesiones anteriores no tienen token CSRF).
6. Probar con un Admon, un Funcionario Lobby y un Op.Garita: login, check-in, reportes, correos, módulo de usuarios.

## Qué cambió

| Hallazgo | Corrección |
| --- | --- |
| C1 CRUD sin autenticación | Módulo de usuarios solo Admon; perfil solo el propio; hashes nunca se envían al navegador. |
| C2 Inyección SQL en login | Sentencia preparada, sin revelar si el usuario existe, nuevo ID de sesión al entrar. |
| C3 Inyección SQL general | Escape central de toda la entrada; parámetros de DataTables forzados a números. |
| C4 / C8 Acceso sin sesión | Los 270 archivos PHP exigen sesión en su primera línea. |
| C5 Credenciales de BD | Fuera del código, en archivo de configuración privado. |
| C6 Claves API y SMTP | 4 claves SendGrid y 2 contraseñas SMTP movidas a la configuración; `.env` eliminados. |
| C7 MD5 | `password_hash` (bcrypt). Las contraseñas MD5 siguen funcionando y se convierten solas en el siguiente login. Las pantallas de usuarios muestran `********`. |
| A1 Roles | Páginas solo Admon: usuarios, configuración, manillas, apartamentos, alertas, edición de parqueo. Op.Garita ya no abre check-in, huéspedes, visitantes, reportes, caja, facturas ni migración (sí planilla, parqueadero, personal y paquetes). |
| A2 Fuerza bruta | 5 fallos en 15 min bloquean el usuario 15 min (20 por IP). Mensaje nuevo en el login. |
| A3 XSS reflejado | 17 archivos: parámetros impresos ahora con `hm_e()`. |
| A4 CSRF | Token por sesión en todo POST. Se inyecta solo en cada página (AJAX, fetch y formularios); no hubo que tocar el JavaScript existente. |
| A5 Datos personales | Eliminadas de `decopass.php` las lecturas reales de cédulas y pasaporte. |
| A6 Subida webcam | Solo JPEG/PNG reales de hasta 3 MB; la carpeta `images_id` no ejecuta scripts. |
| A7 Redirección sin exit | Cubierto: la verificación central siempre termina con `exit`. |
| M1 Errores SQL visibles | 77 archivos: el error se registra en el log y el usuario ve un mensaje genérico. |
| M2 Logs públicos | Log de PHP en `/home/<usuario_hosting>/hm_logs/entidad_php.log` (fuera del sitio); si no se puede crear, en `sistema/seguridad/logs/` (bloqueada). |
| M3 Sesión | Además de lo anterior (ID nuevo al entrar, cookie `HttpOnly`/`Secure`/`SameSite`, logout completo): cierre automático tras 4 h sin actividad, configurable con `sesion_inactividad_min`. |
| M4 CORS | Eliminado `Access-Control-Allow-Origin: *` de `insert_user.php`. |
| M5 Subida de fotos | Huéspedes y placas: solo JPG/PNG/GIF reales de hasta 5 MB, con mensaje claro si se rechaza. |
| M6 Enumeración | El login responde igual y en el mismo tiempo exista o no el usuario. |
| B1 Librerías | jQuery: 16 archivos en 9 versiones (1.6.2 a 3.4.1) reemplazados por jQuery 3.7.1 + Migrate 3.5.2, conservando los nombres de archivo; CDN 3.2.1 cambiado a copia local; cargas duplicadas de jQuery eliminadas en 9 páginas; 4 plugins antiguos ajustados (`.live`, `$.browser`, `.andSelf`). Bootstrap JS 3.3.5/3.3.7 → 3.4.1. PHPMailer: copia duplicada eliminada. Módulo de huellas retirado (usaba `mysql_*`, no funciona desde PHP 7); el botón de huella muestra un aviso. |
| B2 Código muerto | Eliminados `consultas_old/`, `pluginselect_old/`, `fingerprint/`, demos, pruebas, respaldos, zips, ejemplos y documentación de librerías (75 MB → 41 MB). Los dos reportes Excel propios escondidos en `phpexcel/Examples/` pasaron a `reportes_excel/` con control de sesión (antes estaban abiertos a internet). |
| B3 .htaccess | Sin listado de carpetas; bloquea archivos sensibles y carpetas de librerías; cabeceras de seguridad (nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy, HSTS). Probado en Apache. |

## Versión 5 — Diseño "Recepción"

Subir `actualizacion_tema.zip` sobre `sistema/`. No requiere cambios en la base de datos.

- **Encabezado nuevo** en todas las páginas con menú: barra superior con marca, fecha/hora y menú de usuario; menú horizontal con las mismas opciones y permisos por rol (se usa el mismo `menu.php`, restilizado; `verify_menu.js` no cambió).
- **Panel de recepción** en `entrada.php` (roles distintos de Op.Garita): ocupación, llegadas y salidas de hoy, huéspedes en casa, estado Ocupado/Disponible por torre con filtros y buscador, llegadas pendientes (reservas online), salidas pendientes y vencidas con acceso directo a `checkout.php?folio=`, y calendario de 14 días. Op.Garita conserva su pantalla de accesos.
- **Modales**: se anulan los 113 anchos/posiciones fijos; cuatro tamaños que se ajustan a la pantalla; contenido largo con desplazamiento interno; botón cerrar siempre visible; títulos correctos (los ids repetidos `myModalLabel` hacían que todos anunciaran el título del primero); pie de botones fuera del cuerpo; foco en el primer campo. Aplica también a los modales creados desde JavaScript.
- **Estilo global**: formularios, botones, tablas, DataTables, alertify, select2 y pie de página. Fuentes Manrope y Sora incluidas en `fonts/hm/` (no dependen de Google).
- Las páginas sin menú (login, facturas, tarjetas, impresiones) **no** cambian.

**Sin rastro del CSS anterior**: el sistema cargaba hojas de estilo dentro del `<body>` (hasta 3 segundos después de pintar la página), y varias repetidas hasta 4 veces, lo que hacía ver por un instante el diseño viejo. Ahora, en cada página con menú se suben esas hojas al `<head>`, se eliminan las repetidas (incluidas las copias de jQuery, Bootstrap y alertify) y el tema queda de último en la cascada. Además ya no se cargan `boton.css` ni `tabla_estilo.css`, que el tema reemplaza por completo. Verificado con la red ralentizada: la primera pintura ya sale con el diseño nuevo.

**Sin "franja azul" al navegar**: 14 páginas imprimen el menú antes de `<head>` o de `<body>` (por ejemplo Verificar reserva, Consulta general, Planilla), y el navegador lo pintaba un instante con el estilo viejo. Ahora el tema se activa en la primera línea del documento (sobre `<html>`), su CSS y las fuentes se piden de inmediato, y las opciones del menú según el rol se calculan en el servidor con las mismas reglas de `verify_menu.js`, así el menú sale completo desde el primer instante. Verificado cuadro a cuadro con red lenta.

**Paleta "Ola" (modelo 8)**: colores tomados del logo Huésped Manager PMS. Barra superior en degradado del petróleo del pin (`#0F5C80`) al azul de la ola (`#1789BE`), pin del logo en la barra, botones en azul `#1576A8`, detalles en turquesa `#26A9B5` y fondo `#F1F6FA`. Ocupado (coral) y Disponible (verde) no cambian. Todos los colores del tema quedaron en variables; para volver a la paleta anterior (azul marino) sin cambiar nada más: `'skin' => 'recepcion',` en la configuración.

**Volver al diseño anterior** en cualquier momento: agregar `'tema' => 'clasico',` en `config.php` (o en `hm_config/entidad.php`). El menú anterior está en `menu_clasico.php`.

**Probado**: 43 modales abiertos uno por uno a 1440 y 1280 px (ninguno se sale de la pantalla); 75 páginas con menú sin errores de JavaScript nuevos frente al diseño clásico; todas las pruebas de seguridad de las versiones 1–4 siguen pasando.

## Módulo de apartamentos (Edición → Apartamento)

`edit_apto.php` pasa a ser el módulo completo, solo para Admon. "Ingresos → Ingreso de apartamento" (`apto.php`) abre el mismo módulo con el formulario de nuevo apartamento.

- **Apartamentos**: crear, editar y eliminar. No se puede eliminar uno con estadía activa. Si tiene usuarios del portal, pide confirmación y los elimina con sus permisos. No se puede cambiar el número si ya tiene reservas registradas.
- **Usuarios del portal** (`usuarios_reserva`, por `num_apto`): crear, editar, bloquear y eliminar, con rol, identificación, uno o varios correos (separados por `;`) y notificaciones automáticas.
- **Permisos** (`permiso_sistema`): al crear un usuario se crea su fila de permisos; se editan con interruptores por módulo y opción.
- **Clave**:
  - *Cambiar clave*: manual o generada, con opción de enviarla por correo.
  - *Enviar clave*: genera una clave temporal, la envía y solo la guarda si el correo salió bien.
  - La clave se guarda en el **mismo formato que ya usa el portal**, detectado automáticamente (MD5, SHA1, bcrypt o texto). Se puede fijar con `'clave_propietario_formato'` en la configuración. El formato actual se muestra al pie del listado.
- **Correo**: tu estructura de SendGrid (`\SendGrid\Mail\Mail`, remitente `confirmation-reservadelmar@softmanagersolutions.com`, HTML en línea). El primer correo del usuario va como destinatario y los demás en copia.
- **Excel**: plantilla descargable, exportación de lo actual e importación de `.xlsx` o `.csv` (5 MB, 5.000 filas). Antes de guardar se ve una vista previa con nuevos, actualizaciones y errores por fila. En actualizaciones, una celda vacía no cambia el dato existente. Opcionalmente crea usuarios del portal y les envía la clave. El lector no usa PHPExcel y rechaza archivos manipulados (XXE).
- **Configuración opcional** (ver `config.php.ejemplo`): `roles_propietario`, `permisos_etiquetas`, `portal_propietarios_url`, `correo_remitente`, `correo_remitente_nombre`, `clave_propietario_formato`, `sendgrid_host`.
- **Requisitos del servidor**: las extensiones PHP `curl` y `mbstring` (las exige SendGrid) y, para leer o generar `.xlsx`, `zip` y `xml`. Todas vienen normalmente en cPanel. Sin `zip`/`xml`, la plantilla y la exportación salen en CSV y la importación acepta CSV.
- **Base de datos**: no requiere cambios; usa `apartamento`, `usuarios_reserva` y `permiso_sistema` tal como están.

## Configuración (perfil Soporte), entidad, manilla, token TRA y probador de correo

**Pasos al instalar (una sola vez):**
1. Subir `configuracion_soporte.zip` sobre `sistema/`.
2. Ejecutar `sql/configuracion_soporte.sql` en phpMyAdmin, **cambiando `USUARIO_SOPORTE` por su usuario de ingreso**. El script crea la tabla de ajustes, carga el nombre "Reserva del Mar II", agrega la columna `token_tra` y asigna el rol Soporte.
3. Cerrar sesión y volver a entrar: el perfil Soporte se activa al iniciar sesión.
4. Entrar a Usuario → Configuración y revisar nombre, logo, SIRE y correo.

**Perfil Soporte**
- Entra con los permisos de Admon (todas las páginas funcionan igual) y es el único que ve y usa **Configuración**.
- Solo un Soporte puede asignar ese rol, o editar o eliminar a un usuario Soporte; para un Admon esas acciones devuelven 403.

**Configuración**
- **Edificio:** nombre de la entidad (se aplica en todo el sistema), NIT y logo (PNG, JPG o WEBP de hasta 1 MB; no se admite SVG por seguridad). El logo se guarda en la base y lo sirve `entidad_logo.php`.
- **Código y SIRE:** código del establecimiento y código de ciudad, que usa el reporte de migración.
- **Formularios:** mostrar u ocultar la casilla de manilla (#M).
- **Correo:** API key de SendGrid (solo se guarda si SendGrid la acepta; "Probar clave" revisa el permiso "Mail Send"), remitente, nombre del remitente y dirección del portal. La clave guardada aquí tiene prioridad sobre la de `config.php`.
- **Base de datos:** crea la columna `token_tra` si el script SQL no se ejecutó.

**Nombre de la entidad sin textos fijos**
- Las 145 menciones del nombre escritas a mano en 32 archivos (títulos, pies de página, textos legales, asuntos de correo y login) salen ahora de Configuración. Lo mismo el correo remitente y la dirección del portal en las notificaciones antiguas.
- Cada página tiene un título propio, por ejemplo "Ingresos: Check-in · Reserva del Mar II".

**Casilla de manilla**
- Al ocultarla, desaparece de todos los formularios (check-in, adicionar, visitantes y censo, incluidas las filas que se agregan dinámicamente) y los registros se guardan con manilla `0000`, la excepción "sin manilla" que el sistema ya usa. El servidor completa `0000` como respaldo.
- No se toca `num_manilla_rep_h`, que guarda la fecha de nacimiento para la validación por edad.

**Apartamentos**
- **Token TRA** (Tarjeta de Registro Hotelero): columna en el listado con estado "Configurado" o "Pendiente" (el token nunca se muestra), filtro "Sin token TRA", campo en el formulario (vacío conserva el actual; se puede borrar) y columna en la plantilla de Excel.
- **Probador de correo** (Admon): revisa el formato, que el dominio reciba correo y las listas de SendGrid (rebotes, bloqueos, inválidos, spam). Con la casilla marcada, envía un mensaje de prueba y muestra el ID de SendGrid. No envía a correos que están en lista de supresión. Límite: 20 envíos cada 10 minutos por sesión. También hay un botón "Probar correo" en cada apartamento.

## Formularios, contenedores y retiro del CSS obsoleto

Subir `formularios_gentelella.zip` sobre `sistema/` (3 archivos). No requiere cambios en la base de datos.

- **Contenedores (x_panel de Gentelella):** todo formulario, tabla o bloque de contenido va sobre una tarjeta blanca con borde suave, separada del fondo. Si una tarjeta queda dentro de otra, la interior se aplana para no mostrar doble borde.
- **Formularios:** los que usaban etiqueta flotante de 100 px y campos de 200 px fijos pasan a una cuadrícula adaptable, con la etiqueta sobre el campo. Las filas de botones ocupan todo el ancho.
- **Tablas con campos** (registro de huéspedes, visitantes, etc.): cada tabla queda en un contenedor con desplazamiento propio y nunca se sale de su tarjeta. Los campos tienen ancho mínimo y los selects se ajustan a su opción más larga, así no se ven cortados ("Huéspe", "COLOM"). Los iconos blancos de los encabezados ahora se ven.
- **Responsive:** en pantallas pequeñas los formularios pasan a una columna y las tablas se deslizan dentro de su tarjeta.
- **Modales y alertas:** la base de las ventanas modales, que venía de `only_alerts.css`, ahora está en el tema.
- **CSS obsoleto:** en las páginas con el tema ya no se cargan `css/style.css`, `css/caja/style.css`, `todc-bootstrap`, `only_alerts.css`, `demo_table.css`, `demo_page.css`, `style_entrada.css`, `boton.css`, `tabla_estilo.css`, `boton_parqueo.css` ni `csscustom.css`. Todo lo que aportaban está reemplazado en `css/hm-theme.css`. Se conservan Bootstrap 3 (base de Gentelella), los iconos y los plugins (DataTables, select2, datepicker, alertify). En el modo clásico (`'tema' => 'clasico'`) todo sigue como antes.
- **Verificado en las 75 páginas con menú**, a 1440 px de ancho y en celular:

  | Indicador | Antes | Después |
  |---|---|---|
  | Páginas con contenido sin contenedor | 57 | 0 |
  | Páginas con desplazamiento horizontal | 3 | 0 |
  | Páginas con contenido desbordado | 5 | 0 |
  | Selects cortados | 8 páginas | 1 select |
  | Hojas obsoletas cargadas | 29 | 0 |

  Sin errores de JavaScript nuevos; los 46 modales y las pruebas de seguridad siguen igual.

## Registro de adicionales (requestAdc.php) y corrección CSRF

Subir `ajuste_adicionar.zip` sobre `sistema/` (4 archivos).

- **A todo el ancho:** en cualquier página y resolución, las tablas ocupan todo el ancho disponible aunque estén dentro de filas flexibles. Antes la de datos del registro quedaba al 80 % y la de huéspedes al 64 %.
- **Adicionar (`requestAdc.php`):**
  - Subtítulos "Datos del registro" y "Huéspedes registrados".
  - El resumen se muestra como tres indicadores encima de la tabla: total de huéspedes, menores de 2 años y ocupación de adultos.
  - El encabezado de la tabla de huéspedes, que era una fila normal (`tr.head`), tiene el estilo de tabla del tema.
- **Hojas antiguas en fragmentos AJAX:** el fragmento volvía a cargar `boton.css` y una segunda copia de alertify al insertarse; ya no lo hace. Además, cualquier hoja obsoleta que llegue en un fragmento AJAX se descarta automáticamente.
- **Corrección importante (CSRF):** en las pantallas que cargan fragmentos con `<head>` por AJAX (Adicionar, censo, check-out y otras), el script de protección se ejecutaba de nuevo con cada carga y duplicaba la cabecera del token, y el servidor rechazaba los guardados siguientes con 403. Ahora el script se instala una sola vez por página, y el servidor acepta la cabecera repetida solo si todas las copias son el token correcto.

## Selección de apartamento centrada

Subir `seleccion_apto_centrada.zip` sobre `sistema/` (4 archivos).

- **Barras y filtros centrados:** las barras de selección de apartamento ("Apartamento: [lista] [Buscar]") y los filtros pequeños que incluyen el apartamento quedan centrados dentro de su tarjeta. Aplica a Adicionar, Check-out, Edición de check-in, Visitante, Personal (2 pantallas), Zona, Consulta de zonas, Parqueadero, Manillas (2 pantallas) y los 4 reportes por apartamento.
- **Formularios grandes sin cambios:** en Check-in e Ingreso de paquetes el apartamento es un campo más de un formulario grande, así que conserva su posición.
- **En celular:** la etiqueta, la lista y el botón se apilan centrados.
- **Consulta de zonas:** la fila de filtros se rehízo con el componente `.hm-filtros` (cada etiqueta sobre su campo). Se conservan los mismos `id`, `name` y el contenedor del calendario. Con esto se corrigió el último select cortado del sistema ("Reservas de fecha seleccionada").
- **Verificado:** 75 páginas sin desplazamiento horizontal, sin desbordes, sin selects cortados y sin errores de JavaScript nuevos.

## Tablas escritas directamente en la página (Check-out y otras)

Subir `ajuste_tablas_ancho.zip` sobre `sistema/` (3 archivos).

- Algunas páginas escriben la tabla directamente en `<body>`, sin contenedor. Es el caso de "Huéspedes registrados" en Check-out. El tema la pasaba a `display:block` para que se deslizara, pero así las filas se encogían a su contenido y la tabla no llenaba el ancho.
- Ahora esas tablas se envuelven en una tarjeta con desplazamiento propio, conservan su comportamiento de tabla y ocupan el 100 % del ancho en cualquier resolución: a 2.560 px, 2.458 de 2.458 px disponibles. Si en pantallas pequeñas no caben, se deslizan dentro de su tarjeta sin mover la página.
- Los títulos sueltos del tipo "HUESPEDES REGISTRADOS:" se muestran como subtítulo.

## Manuales de usuario (manual.php)

Subir `manuales.zip` sobre `sistema/`. No requiere cambios en la base de datos.

- **Tres manuales en PDF** con capturas del sistema actual:
  - Recepción: 14 páginas.
  - Administrador: 12 páginas.
  - Soporte: 10 páginas.

  Cada uno incluye índice con enlaces y marcadores.
- **`manual.php`:** el menú de usuario › **Manuales** muestra a cada perfil solo lo suyo:

  | Perfil | Manuales |
  |---|---|
  | Lobby, Garita y zona común | Recepción |
  | Administrador | Recepción y Administrador |
  | Soporte | Los tres |

  Cada manual se puede ver en el navegador o descargar.
- **Protección:** la carpeta `manual/` quedó bloqueada para el acceso directo (`manual/.htaccess`). Los PDF solo se entregan por `manual.php`, que verifica sesión y perfil. Antes el manual se podía abrir por URL sin iniciar sesión.
- **Manual anterior:** `manual/manual_usuario.pdf` ya no se enlaza y queda bloqueado. Se puede borrar del servidor.
- **Actualizar un manual:** reemplace el PDF en `manual/` con el mismo nombre (`manual_recepcion.pdf`, `manual_administrador.pdf` o `manual_soporte.pdf`).

## Año automático en los pies de página

Subir `anio_automatico.zip` sobre `sistema/` (19 archivos). No requiere cambios en la base de datos.

- Todos los años escritos a mano en pies de página pasan a `date('Y')` y cambian solos cada año:
  - el pie de todas las páginas (`footer.php`);
  - la pantalla de ingreso;
  - los pies de las descargas;
  - los pies de las tablas de reportes y consultas;
  - el título de los términos de uso;
  - el texto alternativo del logo en facturas e impresiones.
- En total son 38 cambios en 18 archivos. Los rangos del tipo "2023 - 2020" quedan como el año actual.
- No se tocaron los avisos de licencia de terceros (Bootstrap, alertify, DataTables, PHPExcel), que deben conservar su año original.

## Bloqueo de zonas comunes por apartamento

Subir `bloqueo_zonas.zip` sobre `sistema/`. Luego crear la columna de una de dos formas:
- ejecutar `sql/bloqueo_zona.sql` en phpMyAdmin; o
- con un usuario Soporte, en Configuración › Base de datos › **Crear columna**.

- **Columna `apartamento.bloqueo_zona`:** tipo `VARCHAR(2)`, con valor `NO` por defecto, la misma convención que `bloqueado`. Con `SI`, los huéspedes y propietarios del apartamento no pueden reservar zonas comunes.
- **Editable desde el listado de apartamentos:** columna "Zonas comunes" con un interruptor (pide confirmación al bloquear), filtro "Zonas bloqueadas" e indicador. También está en el formulario del apartamento, en la plantilla y en la importación de Excel (acepta SI/NO).
- **Aplicación en la reserva:** en Reservas de zonas, el apartamento bloqueado muestra un aviso y su formulario queda deshabilitado. Además, `reservation_turno.php` rechaza en el servidor cualquier intento de reserva de ese apartamento.
- **Portal de propietarios:** el portal externo (fuera de este código) debe consultar `apartamento.bloqueo_zona` antes de permitir una reserva.
- **Manuales:** se actualizaron los del Administrador y de Soporte.

## Paletas de color con vista previa (Configuración › Apariencia)

Subir `paletas.zip` sobre `sistema/`. No requiere cambios en la base de datos.

- **Las 10 paletas del lienzo de diseño** están disponibles:
  - 4 basadas en el logo: Petróleo del pin, **Ola** (la predeterminada), Turquesa claro y Blanco con ola.
  - 6 generales: Recepción, Gentelella clásico, Caribe, Cartagena, Bosque y Grafito nocturno (modo oscuro).
- **Selección en Configuración (perfil Soporte) › Apariencia:**
  - Cada paleta tiene una miniatura real de la interfaz.
  - Un clic la aplica como vista previa en la pantalla, solo para quien la está revisando.
  - "Aplicar a todo el sistema" la guarda; "Descartar" vuelve a la actual.
- **Colores centralizados:** los colores del tema que estaban escritos a mano (fondos de chips, filas alternas, texto y pin de la barra superior, fondo de ventanas modales, leyenda del panel) pasaron a variables, para que cada paleta los cambie. Las paletas de barra clara usan texto y pin oscuros, y la oscura se verificó en formularios, modales y tablas antiguos.
- **Prioridad:** la paleta elegida en Configuración tiene prioridad. La clave `skin` del archivo de configuración queda como respaldo y acepta los 10 nombres.
- **Manual de Soporte:** se actualizó con la sección Apariencia.

## Eliminación múltiple de apartamentos y clave en la plantilla

Subir `apartamentos_multiple.zip` sobre `sistema/`. No requiere cambios en la base de datos.

**Eliminación múltiple** (Edición › Apartamento):
- Casilla por fila y casilla en el encabezado para seleccionar **todos los apartamentos que muestra el filtro actual** (una torre, una búsqueda o la lista completa).
- La barra indica cuántos están seleccionados y cuántos usuarios del portal se perderían.
- Antes de borrar, una ventana resume cuántos se eliminarán, cuántos usuarios del portal se pierden y cuáles quedan excluidos. Hay que escribir **ELIMINAR** para habilitar el botón.
- Protecciones: los apartamentos con estadía activa nunca se eliminan; el borrado se hace en una transacción junto con sus usuarios del portal y permisos; queda registro en el log del servidor con el usuario que lo hizo y los apartamentos eliminados; máximo 1.000 por operación; solo Admon o Soporte.

**Columna `usuario_clave` en la plantilla de Excel:**
- Guarda la clave del portal en `usuarios_reserva`, cifrada con el mismo formato que usa el portal.
- Si el usuario es nuevo, se crea con esa clave; si ya existe (mismo correo en ese apartamento), se le actualiza.
- Debe tener al menos 8 caracteres con letras y números; si no, la fila se marca con error y no se guarda.
- Con la opción de enviar clave, se envía exactamente la del archivo, no una temporal.
- La exportación nunca incluye claves, y la vista previa no las devuelve al navegador: quedan solo en la sesión del servidor mientras se confirma la importación.
- Recomendación: el archivo de Excel queda con las claves en texto plano; guárdelo en un lugar seguro o bórrelo después de importar.

## Corrección: cierre de los modales del menú y clave mínima de 4

Subir `correccion_modales_clave.zip` sobre `sistema/`. No requiere cambios en la base de datos.

- **Botón cerrar de "Salidas de hoy" (y demás ventanas del menú):** esas ventanas están escritas dentro del `<header>`, y como la barra superior fija crea un contexto de apilamiento, quedaban por debajo del fondo oscuro: el clic caía en el fondo y no en el botón. Ahora esas ventanas se mueven automáticamente fuera del encabezado, y una regla adicional sirve de respaldo. Verificado con clics reales en la X y en "Salir", en "Salidas de hoy" y en "Cerrar sesión".
- **Fondo oscuro de las ventanas modales:** al trasladar la base de los modales al tema, la regla que oscurece el fondo quedaba anulada por otra posterior, así que el fondo no se atenuaba en ninguna ventana. Corregido.
- **Clave del portal: mínimo 4 caracteres.** Antes se exigían 8 con letras y números, lo que impedía importar claves cortas. Ahora se exigen 4 caracteres sin espacios, y se recomiendan 8 o más. Aplica a la importación de Excel, al cambio de clave y al formulario de usuario.

## Nombres internos genéricos (sistema universal)

Subir `nombres_genericos.zip` sobre `sistema/`. **No hay que renombrar nada en el servidor**: el sistema sigue aceptando los nombres anteriores.

- **Archivo de configuración:** el nombre pasa de `reservadelmar2.php` a **`entidad.php`**. El sistema busca primero `hm_config/entidad.php` y, si no existe, `hm_config/reservadelmar2.php`, así que las instalaciones actuales siguen funcionando. Para adoptarlo, basta renombrar el archivo en el servidor cuando se quiera.
- **Registro de errores:** pasa de `reservadelmar2_php.log` a **`entidad_php.log`** (se crea solo).
- **Plantillas y documentación:** `hm_config/entidad.php.ejemplo` y `seguridad/config.php.ejemplo` ya no traen nombres de esta copropiedad ni de la cuenta del hosting: usan `<usuario_hosting>`, `usuario_de_la_base` y `nombre_de_la_base`.
- **Nombre visible de la entidad:** se administra desde Configuración › Edificio, como hasta ahora.
- Se corrigió además que, si falta el archivo de configuración, la página muestre el mensaje `HM-CFG-…` sin intentar aplicar el tema.

## Al desplegar la versión 4
Subir `actualizacion_B1_B3.zip` y **borrar del servidor** lo listado en `ELIMINAR_DEL_SERVIDOR.txt` (subir archivos no borra los viejos).

## Para apps externas
Llamadas sin sesión a `insert_user.php`, `entrega_paquete.php`, `verify_paquete.php` y `select_manilla_update.php` deben enviar la cabecera `X-Api-Key` con el valor `api_key` de la configuración.

## Pendiente
- **XSS almacenado (parte de A3):** los ~2.700 puntos donde se imprimen datos de la BD aún no se escapan. Mitigado por la cookie `HttpOnly` (no se puede robar la sesión con JavaScript), pero conviene aplicar `hm_e()` progresivamente, empezando por nombres de huéspedes y visitantes.
- **C3 definitivo:** migrar consultas a sentencias preparadas con `hm_db()`.
- la carpeta `../api/facturas/` (enlazada desde el menú) no venía en el zip y probablemente tiene los mismos problemas.
- Errores previos no tocados: `caja.php`, `factura_adc.php` y `factura_visita.php` usan funciones `mysql_*` (eliminadas en PHP 7) y no funcionan; `insert_user.php` usa `$con` (nunca funcionó); `insert_reservas_vis.php` e `insert_reservas_empleado.php` requieren `sistema/mailer/`, que no venía en el zip (si no existe en el servidor, esos correos fallan).
- PHPExcel (abandonado) se mantiene solo para escribir los 2 reportes Excel; no lee archivos subidos, así que su riesgo es bajo. Funciona en PHP 7; antes de pasar a PHP 8 hay que migrar esos 2 reportes a PhpSpreadsheet.
- Hay 14 archivos PHP sin ninguna referencia (reportes y facturas antiguas) que no se borraron por si se abren con enlace directo: fact_parqueo, factura_adc, factura_visita, rel_aptos, reporte_2023, reporte_apto_org, reporte_azules, reporte_descarga, reporte_gral, reporte_inv_2023, reporte_over_book, reporte_visitas, success, up_huesped.
- Requiere PHP 7.0+ con mysqlnd (estándar en cPanel).
