mapas.html 10.3 KB
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SISTEMA GIS - MapLibre 3D Elite</title>
    <script>
        if (!localStorage.getItem('jwt')) {
            window.location.href = '/gis-geoserver/login';
        }
    </script>
    <!-- MapLibre GL JS -->
    <script src="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js"></script>
    <link href="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css" rel="stylesheet" />
    <style>
        * { box-sizing: border-box; }
        body, html {
            height: 100%;
            margin: 0;
            font-family: 'Inter', Arial, sans-serif;
            background-color: #0f172a;
            color: #fff;
            overflow: hidden;
        }
        .header {
            height: 60px;
            background: #1e293b;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0 20px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.5);
            font-weight: bold;
            position: relative;
            z-index: 1001;
        }
        .logout-btn {
            background: rgba(255,255,255,0.1);
            border: 1px solid rgba(255,255,255,0.3);
            color: white;
            padding: 8px 16px;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
        }
        .logout-btn:hover { background: #ef4444; border-color: #ef4444; }
        .app-container { display: flex; height: calc(100vh - 60px); }
        .sidebar {
            width: 280px;
            background: #0f172a;
            border-right: 1px solid rgba(255,255,255,0.1);
            overflow-y: auto;
            padding: 20px;
            flex-shrink: 0;
        }
        .menu-title {
            font-size: 11px;
            color: #64748b;
            font-weight: 700;
            text-transform: uppercase;
            margin: 25px 0 10px;
            letter-spacing: 1px;
        }
        .menu-item {
            padding: 12px 16px;
            border-radius: 10px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 12px;
            color: #94a3b8;
            transition: all 0.2s;
            margin-bottom: 4px;
            border: 1px solid transparent;
            text-decoration: none;
        }
        .menu-item:hover { background: rgba(255,255,255,0.05); color: #fff; }
        .menu-item.active {
            background: rgba(59, 130, 246, 0.1);
            color: #3b82f6;
            border-color: rgba(59, 130, 246, 0.3);
        }
        #map { flex: 1; position: relative; }
        .map-legend {
            position: absolute;
            bottom: 30px;
            right: 20px;
            background: rgba(15, 23, 42, 0.85);
            padding: 18px;
            border-radius: 16px;
            border: 1px solid rgba(255,255,255,0.1);
            color: #f1f5f9;
            font-size: 11px;
            z-index: 1000;
            backdrop-filter: blur(12px);
            box-shadow: 0 10px 30px rgba(0,0,0,0.5);
            max-width: 240px;
        }
        .legend-item { display: flex; align-items: center; margin-bottom: 8px; gap: 10px; }
        .legend-color { width: 30px; height: 12px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.1); }
    </style>
</head>
<body>
    <div class="header">
        <div id="map-title" class="header-title">VISTA CARTOGRÁFICA GENERAL</div>
    </div>
    <div class="app-container">
        <div class="sidebar">
            <div id="stats-dashboard">
                <div class="menu-title">Resumen Municipal</div>
                <div class="stat-card" style="width: 100%; padding: 15px; background: rgba(255,255,255,0.03); border-radius: 12px; margin-bottom: 20px;">
                    <div class="stat-label" style="font-size: 11px; color: #64748b;">Total Lotes</div>
                    <div id="stat-lotes" class="stat-value" style="font-size: 24px; font-weight: 700;">...</div>
                </div>
            </div>

            <div class="menu-title">Control de Gestión</div>
            <div id="menu-reset" class="menu-item active" onclick="resetMap()">Capas Base</div>
            <select id="base-layer-select" style="background: #1e293b; color: white; border: 1px solid #334155; padding: 10px; width: 100%; border-radius: 8px; margin-top: 10px;">
                <option value="dark">CartoDB Dark</option>
                <option value="streets">OpenStreetMap</option>
                <option value="satellite">Esri Satélite</option>
            </select>

            <div class="menu-title">Mapas Tributarios</div>
            <div id="menu-ultimo-pago" class="menu-item" onclick="setHeatmap('ultimo-pago')">Por Último Pago</div>
            <div id="menu-percentiles" class="menu-item" onclick="setHeatmap('percentiles')">Por Monto Adeudado</div>
        </div>

        <div id="map">
            <div class="map-legend" id="legend" style="display: none;">
                <div id="legend-content"></div>
                <div style="font-size: 10px; margin-top: 10px; border-top: 1px solid #333; padding-top: 5px; color: #888;">Fuente: Sistema SIGEM</div>
            </div>
        </div>
    </div>

    <script>
        const entidad = localStorage.getItem('entidad') || '505';
        const token = localStorage.getItem('jwt');

        const map = new maplibregl.Map({
            container: 'map',
            style: {
                version: 8,
                sources: {
                    'raster-tiles': {
                        type: 'raster',
                        tiles: ['https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'],
                        tileSize: 256,
                        attribution: '&copy; CartoDB'
                    }
                },
                layers: [{
                    id: 'basemap',
                    type: 'raster',
                    source: 'raster-tiles',
                    minzoom: 0,
                    maxzoom: 20
                }]
            },
            center: [parseFloat(localStorage.getItem('map_lng')) || -56.443, parseFloat(localStorage.getItem('map_lat')) || -25.449],
            zoom: parseInt(localStorage.getItem('map_zoom')) || 15
        });

        map.addControl(new maplibregl.NavigationControl({ position: 'bottom-left' }));

        map.on('load', () => {
            initGisSources();
            loadMunicipalStats();
        });

        function initGisSources() {
            if (!map.getSource('lotes-mvt')) {
                map.addSource('lotes-mvt', {
                    type: 'vector',
                    tiles: [`${window.location.origin}/gis-geoserver/gwc/service/tms/1.0.0/sigem:vw_lotes_morosidad_${entidad}@XYZ-900913@pbf/{z}/{x}/{y}.pbf`],
                    scheme: 'tms'
                });
            }

            if (!map.getLayer('lotes-layer')) {
                map.addLayer({
                    id: 'lotes-layer',
                    type: 'fill',
                    source: 'lotes-mvt',
                    'source-layer': `vw_lotes_morosidad_${entidad}`,
                    paint: {
                        'fill-color': 'rgba(59, 130, 246, 0.1)',
                        'fill-outline-color': 'rgba(255, 255, 255, 0.3)'
                    }
                });
            }
        }

        function setHeatmap(type) {
            document.querySelectorAll('.menu-item').forEach(el => el.classList.remove('active'));
            const legendEl = document.getElementById('legend');
            const legendContent = document.getElementById('legend-content');
            legendEl.style.display = 'block';

            if (type === 'ultimo-pago') {
                document.getElementById('menu-ultimo-pago').classList.add('active');
                legendContent.innerHTML = `
                    <strong style="color: #60a5fa;">ÚLTIMO PAGO</strong><br><br>
                    <div class="legend-item"><div class="legend-color" style="background: #6b9070;"></div> 2026</div>
                    <div class="legend-item"><div class="legend-color" style="background: #b5c47a;"></div> 2025</div>
                    <div class="legend-item"><div class="legend-color" style="background: #ffd966;"></div> 2024</div>
                    <div class="legend-item"><div class="legend-color" style="background: #f08060;"></div> 2023</div>
                    <div class="legend-item"><div class="legend-color" style="background: #d05660;"></div> 2022</div>
                    <div class="legend-item"><div class="legend-color" style="background: #a91d1d;"></div> 2021</div>
                    <div class="legend-item"><div class="legend-color" style="background: #64748b;"></div> SIN DEUDA</div>
                `;
                map.setPaintProperty('lotes-layer', 'fill-color', [
                    'step', ['to-number', ['get', 'ultimo_pago'], 0],
                    '#a91d1d', 2021, '#a91d1d', 2022, '#d05660', 2023, '#f08060', 2024, '#ffd966', 2025, '#b5c47a', 2026, '#6b9070'
                ]);
            } else if (type === 'percentiles') {
                document.getElementById('menu-percentiles').classList.add('active');
                legendContent.innerHTML = `<strong style="color: #60a5fa;">MONTO ADEUDADO</strong><br><br>...`;
                map.setPaintProperty('lotes-layer', 'fill-color', [
                    'step', ['to-number', ['get', 'trb_total_deuda'], 0],
                    '#6b9070', 355629, '#b5c47a', 718985, '#ffd966', 1231877, '#f08060', 2134820, '#a91d1d'
                ]);
            }
        }

        function resetMap() {
            document.querySelectorAll('.menu-item').forEach(el => el.classList.remove('active'));
            document.getElementById('menu-reset').classList.add('active');
            document.getElementById('legend').style.display = 'none';
            map.setPaintProperty('lotes-layer', 'fill-color', 'rgba(59, 130, 246, 0.1)');
        }

        async function loadMunicipalStats() {
            try {
                const res = await fetch(`/gis-geoserver/api/gis/entidad/${entidad}/resumen`, {
                    headers: { 'Authorization': `Bearer ${token}` }
                });
                const data = await res.json();
                document.getElementById('stat-lotes').innerText = data.total_lotes.toLocaleString();
            } catch (e) { console.error(e); }
        }
    </script>
</body>
</html>
GitLab Appliance - Powered by TurnKey Linux