# TEST REPORT — Sistema ISP V1.0.0

Este reporte corresponde a la revisión final de Parte 10. No presenta una inspección de código como si fuera una prueba ejecutada.

## Entorno disponible durante la revisión

- PHP CLI: 8.4.23.
- PDO disponible.
- `pdo_mysql`: **no disponible** en el entorno de revisión.
- Servidor HTTP de desarrollo de PHP: disponible.
- Node.js: disponible para comprobación sintáctica de JavaScript.
- Chromium: instalado, pero el intento headless de esta revisión no completó dentro del timeout del entorno.
- Cliente/servidor MySQL real: no disponible.

Por lo anterior, la persistencia real MySQL, la importación del schema sobre XAMPP, el navegador interactivo real y touch no se declaran como ejecutados.

## EJECUTADO Y COMPROBADO

| Prueba | Resultado | Observación |
|---|---|---|
| PHP lint completo | OK | 55 archivos PHP pasaron `php -l` con 0 errores. |
| JavaScript sintaxis | OK | 4 archivos JS pasaron `node --check`. |
| CSS estructura básica | OK | Llaves balanceadas en `app.css` y `map.css`. |
| Schema — tablas esperadas | OK | Detectadas `users`, `clients`, `network_points`, `network_links`, `coverage_sectors`. |
| Schema — orden FK Cliente→Punto | OK | `network_points` se crea antes del `ALTER TABLE clients` que agrega la FK. |
| Schema — datos ficticios | OK | No existen `INSERT INTO` en `schema.sql`. |
| Schema — InnoDB/utf8mb4/FKs | OK | Validación estática satisfactoria. |
| GET `/login` | OK | HTTP 200 con servidor PHP local. |
| Rutas privadas sin login | OK | `/clients`, `/points`, `/links`, `/sectors`, `/map` respondieron 302 hacia login. |
| APIs mapa sin login | OK | Clients/Points/Links/Sectors/Pending respondieron 401 JSON. |
| Search sin login | OK | `/api/search?q=ab` respondió 401 JSON. |
| Topology sin login | OK | APIs Cliente/Punto respondieron 401 JSON. |
| API inexistente | OK | 404 JSON controlado. |
| Ruta web inexistente | OK | 404 controlado. |
| CSRF inválido en login | OK | POST devolvió 419. |
| Headers de seguridad | OK | `nosniff`, `DENY`, Referrer-Policy, Permissions-Policy, COOP y CSP mínima presentes. |
| Cookie en HTTP local | OK | `HttpOnly` + `SameSite=Lax`; no se fuerza `Secure` en HTTP. |
| Topología simulada | OK | Principal directo, varios saltos, ciclos, múltiples rutas, enlaces inactivos y dependencias pasaron. |
| Relación Cliente→Punto simulada | OK | Asignar y quitar asignación (`NULL`) pasaron. |
| Búsqueda global simulada | OK | 4 categorías, teléfono normalizado, límites, comodines LIKE y registros sin ubicación pasaron. |
| Enlaces simulados | OK | A→B, duplicados, inverso, A→A, estados y mapa incompleto pasaron. |
| Sectores simulados | OK | dirección/apertura/range/cardinales/estados/XSS pasaron. |
| Throttle login | OK | Tras 5 fallos se bloquea temporalmente; `clear()` restaura acceso. |
| Deep links inválidos | OK | IDs malformados/negativos y foco ambiguo se manejaron sin 500. |
| Backup CLI — mecánica | OK | Ejecutado con `mysqldump` simulado; generó SQL privado no vacío. |
| Restore CLI — protección | OK | Sin `--confirm` terminó con código 2 y no restauró. |
| Restore CLI — mecánica | OK | Ejecutado con cliente MySQL simulado; el SQL llegó íntegro al proceso destino. |
| Limpieza del proyecto | OK | Sin ZIP históricos, `.git`, backups reales, logs, `.tmp`/`.bak` o `config/database.php`. |
| Código de depuración | OK | No se encontraron `console.log`, `console.debug`, `var_dump`, `print_r`, TODO/FIXME/HACK en runtime. |

## REVISADO POR CÓDIGO

| Área | Resultado | Observación |
|---|---|---|
| Instalación nueva | Revisado | `schema.sql` representa el estado V1 y el administrador se crea por CLI. |
| Administrador | Revisado | CLI-only, valida datos, usa `password_hash()` y no imprime la contraseña. |
| Login | Revisado | `password_verify()`, rehash opcional, usuario ACTIVE y error genérico. |
| Sesión | Revisado | `session_regenerate_id(true)`, strict mode, timeout y revalidación del usuario. |
| Logout | Revisado | POST + CSRF + destrucción de sesión/cookie. |
| CSRF | Revisado | Las operaciones de escritura de Clientes/Puntos/Enlaces/Sectores/ubicaciones/asignación/logout pasan por validación CSRF. |
| SQL | Revisado | PDO centralizado, prepares reales y valores parametrizados; fragmentos dinámicos de búsqueda/filtros se generan desde lógica interna. |
| XSS | Revisado | `e()`/`htmlspecialchars` en vistas y construcción DOM con `textContent`/`createElement`; sin `innerHTML` para datos dinámicos. |
| Coordenadas | Revisado | Latitud [-90,90], longitud [-180,180] en servidor. |
| Enlaces | Revisado | origen≠destino, existencia y duplicado exacto. |
| Sectores | Revisado | dirección [0,360), apertura (0,360], range opcional >0. |
| Cliente→Punto | Revisado | FK válida o `NULL`; no se infiere por proximidad/Sector. |
| Punto Principal | Revisado | La lógica permite Clientes directos y mantiene máximo un Principal mediante el flujo existente. |
| TopologyService | Revisado | Usa enlaces registrados, conjuntos `visited`, límites de profundidad/estados y deduplicación. |
| SearchService | Revisado | Queries preparadas, límites y escape de comodines. |
| APIs | Revisado | Todas las APIs actuales exigen `requireApiAuth()`. |
| Errores | Revisado | Respuesta genérica y logging en `storage/logs`; no se muestra SQL/trace al usuario. |
| Mapa | Revisado | Una instancia MapLibre, sources/layers reutilizados, clustering, sin polling ni request por marcador. |
| Backup/restore | Revisado | CLI-only, fuera de `public`, contraseña mediante archivo temporal y restore explícito. |
| `.gitignore` | Revisado | Ignora DB config real, logs, throttle y backups reales. |
| Web root | Revisado | `public/` es la raíz recomendada; raíz del proyecto bloquea directorios internos mediante `.htaccess`. |
| Responsive/accesibilidad | Revisado | CSS incluye breakpoints, safe areas, `dvh/svh`, focus visible y reduced motion; falta certificación en dispositivo real. |

## REQUIERE PRUEBA EN XAMPP DEL USUARIO

Estas pruebas son obligatorias para aprobar operativamente la V1 porque el entorno de revisión no dispone de MySQL real/pdo_mysql ni de un navegador interactivo fiable.

### 1. Instalación limpia

1. Descomprime el ZIP en `C:\xampp\htdocs\sistema-isp`.
2. Inicia Apache/MySQL.
3. Crea `sistema_isp` en phpMyAdmin.
4. Copia `config/database.example.php` a `config/database.php`.
5. Importa `database/schema.sql`.
6. Ejecuta `C:\xampp\php\php.exe scripts\create_admin.php`.
7. Abre `http://localhost/sistema-isp/public/`.

**Pasa** si schema importa sin errores, el administrador se crea y puedes iniciar sesión.

### 2. Flujo funcional crítico

Con datos temporales controlados creados por ti:

1. crea y ubica Punto Principal;
2. crea Cliente y asígnalo directamente al Principal;
3. crea Punto A y Enlace `Principal → A`;
4. asigna otro Cliente a A;
5. crea Sectores en Principal/A;
6. prueba ruta al Principal y dependencias;
7. prueba búsqueda por nombre/teléfono/Punto;
8. edita y desactiva/reactiva Cliente/Punto/Enlace/Sector;
9. prueba deep links y Pendientes de ubicar.

**Pasa** si los datos persisten al recargar y el mapa refleja exactamente las relaciones registradas sin inventar geometría.

### 3. Mapa/browser

Prueba Chrome/Edge/Firefox y, si usarás teléfono, Safari/Chrome móvil.

Resoluciones recomendadas: 1366, 1024, 768, 430, 390 y 360 px; portrait y landscape.

**Pasa** si búsqueda, capas, filtros, clustering, bottom sheet, ubicación, creación visual de Enlace/Sector y touch funcionan sin errores permanentes de consola ni scroll horizontal roto.

### 4. Backup real

Con MySQL/XAMPP activo:

```bat
C:\xampp\php\php.exe scripts\backup.php
```

**Pasa** si crea un `.sql` no vacío en `storage\backups` y `mysqldump` termina sin error.

### 5. Restore real en BASE DE PRUEBA SEPARADA

No uses tu base principal para esta validación.

1. crea una base de prueba;
2. apunta temporalmente `config/database.php` a esa base;
3. ejecuta:

```bat
C:\xampp\php\php.exe scripts\restore.php storage\backups\ARCHIVO.sql --confirm
```

4. revisa tablas y datos restaurados.

**Pasa** si el proceso termina correctamente y la base de prueba contiene el respaldo esperado.

### 6. Consola y Network

En `/map` abre DevTools:

- Console: sin errores persistentes de JavaScript/MapLibre;
- Network: sin loops, polling, 404/500 involuntarios ni request individual por marcador.

## NO APLICA

- Pagos/facturación.
- MikroTik/RouterOS.
- IP/MAC/equipos.
- Monitoreo de señal/velocidad.
- Cliente→Sector.

No forman parte del alcance V1.

## Resultado de revisión

No se detectó un fallo crítico conocido en el código inspeccionado o en las pruebas que sí pudieron ejecutarse.

La aprobación operativa completa queda condicionada a las pruebas MySQL/XAMPP y navegador real indicadas arriba.


---

# Ajuste final V1 · Satélite + Eliminaciones

## EJECUTADO Y COMPROBADO

- `config/map.php` sin API key: Calles queda habilitado y Satélite queda deshabilitado con mensaje de configuración.
- Los placeholders `TU_API_KEY_REAL`, `YOUR_MAPTILER_API_KEY` y `YOUR_MAPTILER_API_KEY_HERE` se interpretan como key no configurada.
- Con `MAPTILER_API_KEY` no vacía de prueba, la configuración habilita Satélite y genera el endpoint oficial `maps/satellite-v4/style.json` con zoom máximo 22.
- PHP lint de todos los archivos PHP modificados por el ajuste Satélite: sin errores de sintaxis.
- Sintaxis de `public/js/map.js`: correcta mediante `node --check`.
- ZIP final: integridad comprobada y sin credenciales reales, `config/database.php`, `config/map.local.php`, backups reales, logs ni ZIP internos.

## REVISADO POR CÓDIGO

- El selector **Mapa base** presenta Calles y Satélite; sin key Satélite permanece deshabilitado de forma visible.
- El cambio Calles ↔ Satélite mantiene MapLibre y utiliza `map.setStyle()`; `style.load` reutiliza `setupSpatialLayers()` para reconstruir las sources/layers operativas existentes.
- Antes de aplicar Satélite, el frontend solicita el Style JSON de MapTiler. Un HTTP 401/403/otro fallo no sustituye el mapa activo y muestra un estado controlado.
- Si un cambio de style iniciado falla después de aplicarse, el código intenta restaurar el mapa base previamente activo.
- Un error posterior de la capa satelital deja disponible el selector para regresar a Calles y muestra un aviso no fatal.
- Clientes, Puntos, Enlaces, Sectores, clustering y el flujo actual de ubicación no fueron rediseñados por este ajuste.
- No se añadió Híbrido.
- El Centro de Eliminación, autenticación, topología, búsqueda y backup/restore no recibieron cambios funcionales.

## REQUIERE PRUEBA EN XAMPP DEL USUARIO

- **Satélite con una API key MapTiler real:** confirmar carga efectiva de imágenes satelitales y el detalle disponible en la zona de operación.
- Cambiar Calles → Satélite → Calles → Satélite con Clientes, Puntos, Enlaces, Sectores y clustering reales.
- Probar Satélite durante Ubicar/Editar ubicación de Cliente y Punto sin perder el estado actual de la selección.
- Probar una key inválida/restringida y confirmar que Calles permanece utilizable y aparece el mensaje controlado.
- Revisar el selector en desktop, tablet y 430/390/375/360 px.

## NO APLICA

- Híbrido: explícitamente fuera del alcance de este ajuste.
- Cambios de precisión, mira central, geocodificación o nuevo flujo de coordenadas: fuera del alcance y no implementados.

### Corrección de falso rechazo de Satélite

**EJECUTADO Y COMPROBADO**

- Sintaxis de `public/js/map.js` verificada con `node --check`.
- Prueba aislada del ciclo de eventos confirmó que un `style.load` emitido inmediatamente después de `setStyle()` se pierde si el listener se registra después, mientras que el flujo corregido que registra el listener primero lo captura correctamente.
- La integración corregida ya no predescarga el Style JSON para pasarlo como objeto: MapLibre recibe directamente la URL oficial de MapTiler mediante `map.setStyle(styleUrl)`.
- El tiempo de espera para un cambio real de mapa base se amplió de 15 a 30 segundos.

**REVISADO POR CÓDIGO**

- Los eventos `error` secundarios de MapLibre ya no se interpretan como fallo total de Satélite ni provocan retorno automático a Calles.
- `style.load` continúa ejecutando `setupSpatialLayers()` para restaurar Clientes, Puntos, Enlaces, Sectores, clusters y resaltados sin duplicar listeners.
- El flujo de ubicación y el Centro de Eliminaciones no fueron modificados.

**REQUIERE PRUEBA EN XAMPP DEL USUARIO**

- Con la API key MapTiler real ya configurada: Calles → Satélite → Calles → Satélite, comprobando imágenes satelitales y capas operativas reales en el navegador.

## Corrección Satélite predeterminado + restauración de topología

### EJECUTADO Y COMPROBADO

- Sintaxis de `public/js/map.js` validada con `node --check`.
- `php -l` ejecutado sobre todos los PHP del proyecto después del cambio.
- Prueba aislada confirmó que `setupSpatialLayers()` vuelve a ejecutar la reconstrucción aunque un mapa simulado reporte `isStyleLoaded() === false`, reproduciendo la condición que antes bloqueaba la restauración durante `style.load`.
- Con configuración MapTiler simulada, la lógica de selección inicial prioriza `satellite`; sin Satélite habilitado, cae a `streets`.
- Revisión estática confirmó que el grupo Enlaces incluye la nueva capa de contraste y que su visibilidad sigue controlada por el mismo toggle.

### REVISADO POR CÓDIGO

- Causa encontrada: `style.load` llamaba `setupSpatialLayers()`, pero esta salía inmediatamente por `!map.isStyleLoaded()`. MapLibre puede emitir `style.load` mientras `isStyleLoaded()` aún es falso por recursos/tile managers pendientes; las sources/layers propias no se reponían tras `setStyle()`.
- La restauración mantiene una sola source por entidad y usa `map.getSource()` / `map.getLayer()` antes de crear, evitando duplicados en cambios repetidos Satélite ↔ Calles.
- Se restauran Sectores, Enlaces, Clientes/clusters, Puntos, topología, previews y highlights, y después se reaplica el estado de los toggles.
- El flujo de ubicación, Centro de Eliminaciones, CRUD, base de datos, topología backend y API key no fueron modificados.
- Satélite es el estilo inicial cuando está habilitado; Calles continúa como alternativa y fallback.

### REQUIERE PRUEBA EN XAMPP DEL USUARIO

- Abrir `/map` con la API key real y confirmar que inicia directamente en Satélite.
- Confirmar con datos reales que Enlaces, Puntos, Clientes, Sectores, clusters, rutas y dependencias permanecen visibles tras al menos cinco cambios Satélite ↔ Calles.
- Confirmar fallback a Calles ante un fallo real del proveedor y revisar Console para ausencia de errores de layers/sources duplicados.
- Confirmar en móvil (~390 px) Satélite predeterminado y Enlace visible.

