1/** 2 * f-svg-sync.js â Delta sync entre allDrawnLines y DOMCache 3 * 4 * REGLA: NUNCA usar innerHTML para limpiar el SVG overlay. 5 * Solo .remove() elemento por elemento. 6 * REGLA: Solo crear lo que es nuevo, solo borrar lo que ya no existe. 7 * REGLA: #kpk-draw-overlay siempre debe ser el lastElementChild del SVG. 8 * 9 * Tipos SVG soportados: 10 * lote-libre, lote-organico â #layer-lotes 11 * calle â #layer-calles-asfalto 12 * calle-curva-arq2 â #layer-calles-arq2 13 */ 14 15'use strict'; 16 17(function() { 18 19 const SVG_NS = 'http://www.w3.org/2000/svg'; 20 21 // Mapeo tipo â id de capa SVG destino 22 const LAYER_MAP = { 23 'lote-libre': 'layer-lotes', 24 'lote-organico': 'layer-lotes', 25 'calle': 'layer-calles-asfalto', 26 'calle-curva-arq2': 'layer-calles-arq2', 27 'calle-retorno': 'layer-calles-asfalto', 28 'division-curva': 'layer-divisiones', 29 'franja-grupo': 'layer-lotes', 30 'franja-curva-grupo': 'layer-lotes', 31 'costura': 'layer-calles-bordes', 32 'kprano-capsule': 'layer-lotes', 33 'fila-variable-lote': 'layer-lotes', 34 }; 35 36 /** 37 * Crea un elemento SVG con namespace correcto (Lección cross-browser). 38 */ 39 function _createSVGEl(tag) { 40 return document.createElementNS(SVG_NS, tag); 41 } 42 43 /** 44 * ReenvÃa un evento al canvas de Pannellum para no bloquear el arrastre de la cámara 45 * cuando el usuario hace clic sobre un polÃgono con pointer-events: all. 46 */ 47 function _forwardToPannellum(e) { 48 const canvas = document.querySelector('.pnlm-canvas'); 49 if (canvas) { 50 // Clonamos el evento para el canvas 51 let clonedEvent; 52 if (e.constructor.name === 'TouchEvent') { 53 clonedEvent = new TouchEvent(e.type, e); 54 } else { 55 clonedEvent = new MouseEvent(e.type, e); 56 } 57 canvas.dispatchEvent(clonedEvent); 58 } 59 } 60 61 /** 62 * Crea el grupo SVG (<g>) para una lÃnea. 63 * @param {Object} line â entrada de allDrawnLines 64 * @returns {SVGGElement} 65 */ 66 function _createLineGroup(line) { 67 const g = _createSVGEl('g'); 68 g.setAttribute('data-id', line.id); 69 g.setAttribute('data-tipo', line.tipo); 70 g.classList.add('ferrari-line-group'); 71 72 73 const tipo = line.tipo; 74 const estado = line.estado || 'disponible'; 75 76 const isLoteInteractivo = (tipo === 'lote-libre' || tipo === 'lote-organico' || tipo === 'franja-grupo' || tipo === 'kprano-capsule'); 77 if (isLoteInteractivo) { 78 g.classList.add('lote-interactivo'); 79 g.style.pointerEvents = 'all'; 80 g.style.cursor = 'pointer'; 81 g.style.userSelect = 'none'; 82 g.style.webkitUserDrag = 'none'; 83 84 // Forward mousedown/touchstart para permitir drag de la cámara 85 // Se quita passive para poder hacer preventDefault si es necesario en otras capas 86 g.addEventListener('mousedown', (e) => { 87 e.preventDefault(); // Evita que el navegador intente arrastrar el SVG (efecto fantasma) 88 _forwardToPannellum(e); 89 }, { passive: false }); 90 91 g.addEventListener('touchstart', (e) => { 92 _forwardToPannellum(e); 93 }, { passive: true }); 94 95 // Click Nativo para la herramienta Smart Pin 96 g.addEventListener('click', (e) => { 97 if (window.FerrariAddPin && window.FerrariAddPin.isActive()) { 98 e.stopPropagation(); 99 let coords = null; 100 try { 101 const viewer = window.Ferrari && window.Ferrari.viewer; 102 if (viewer) coords = viewer.mouseEventToCoords(e); 103 } catch (err) { /* usar centro automático como fallback */ } 104 window.FerrariAddPin.injectPin(line.id, coords); 105 } 106 }); 107 108 // Glow futurista en vértices al pasar el cursor / pointer sobre el lote 109 g.addEventListener('pointerenter', () => { 110 g.classList.add('is-pointer'); 111 if (window.FerrariSVGPaths && window.FerrariSVGPaths.setHoveredLote) { 112 window.FerrariSVGPaths.setHoveredLote(line.id); 113 } 114 }); 115 g.addEventListener('pointerleave', () => { 116 g.classList.remove('is-pointer'); 117 if (window.FerrariSVGPaths && window.FerrariSVGPaths.setHoveredLote) { 118 window.FerrariSVGPaths.setHoveredLote(null); 119 } 120 }); 121 } 122 123 // âââ 2. Clases base ââââââââââââââââââââââââââââââââââââââââââââââââ 124 g.classList.add(`path-${tipo}`); 125 126 // Solo aplicar clase de estado premium si tiene el Smart Pin activado 127 if (line.hasSmartPin) { 128 g.classList.add(`status-${estado}`); 129 } 130 131 // âââ 3. Paths principales âââââââââââââââââââââââââââââââââââââââââ 132 if (tipo === 'calle' || tipo === 'calle-curva-arq2') { 133 const p1 = _createSVGEl('path'); 134 p1.classList.add('path-calle-edge'); 135 g.appendChild(p1); 136 137 const p2 = _createSVGEl('path'); 138 p2.classList.add('path-calle-centro'); 139 g.appendChild(p2); 140 } 141 else if (tipo === 'calle-retorno') { 142 const p1 = _createSVGEl('path'); 143 p1.classList.add('path-calle-edge'); 144 g.appendChild(p1); 145 } 146 else if (tipo === 'division-curva') { 147 const p1 = _createSVGEl('path'); 148 p1.classList.add('path-division-curva-line'); 149 g.appendChild(p1); 150 } 151 else if (tipo === 'lote-libre' || tipo === 'lote-organico') { 152 const p1 = _createSVGEl('path'); 153 p1.classList.add('path-lote'); 154 g.appendChild(p1); 155 } 156 else { 157 const p1 = _createSVGEl('path'); 158 p1.classList.add('path-default'); 159 g.appendChild(p1); 160 } 161 162 // Smart Pins ahora son elementos HTML gestionados por f-smart-pins.js. 163 // No se crean <g> SVG para pins â evita repintado de capa SVG en cada frame. 164 165 return g; 166 } 167 168 /** 169 * syncSVGElements â Delta sync entre allDrawnLines y DOMCache.paths 170 * 171 * Algoritmo: 172 * 1. Construir Set de ids actuales en allDrawnLines 173 * 2. Para cada id en allDrawnLines que NO está en DOMCache â crear y añadir 174 * 3. Para cada id en DOMCache que NO está en allDrawnLines â .remove() y borrar del cache 175 * 4. Mover #kpk-draw-overlay al lastElementChild 176 * 5. Marcar lastSyncedVersion = DOMCache.version 177 */ 178 function syncSVGElements() { 179 const lines = window.allDrawnLines; 180 const cache = window.DOMCache.paths; 181 182 // ââ Paso 1: Set de ids actuales ââââââââââââââââââââââââââââââââââ 183 const currentIds = new Set(lines.map(l => l.id)); 184 185 // ââ Paso 2: Crear elementos nuevos âââââââââââââââââââââââââââââââ 186 for (const line of lines) { 187 if (!cache.has(line.id)) { 188 const layerId = LAYER_MAP[line.tipo] || 'layer-lotes'; 189 const layer = document.getElementById(layerId); 190 if (!layer) { 191 console.warn('[Ferrari/SVGSync] Capa no encontrada:', layerId); 192 continue; 193 } 194 195 const gNode = _createLineGroup(line); 196 layer.appendChild(gNode); 197 198 cache.set(line.id, { 199 gNode, 200 pathEls: Array.from(gNode.querySelectorAll('path')), 201 pinGroup: null, // Smart Pins migrados a HTML overlay (f-smart-pins.js) 202 tipo: line.tipo 203 }); 204 // Notificar al HTML overlay de Smart Pins que debe reconstruirse 205 if (window.FerrariSmartPins) window.FerrariSmartPins.markDirty(); 206 } 207 } 208 209 // ââ Paso 3: Eliminar elementos que ya no existen âââââââââââââââââ 210 for (const [id, entry] of cache.entries()) { 211 if (!currentIds.has(id)) { 212 // NUNCA innerHTML â usar .remove() directo (Lección 4) 213 if (entry.gNode && entry.gNode.parentNode) { 214 entry.gNode.remove(); 215 } 216 cache.delete(id); 217 } 218 } 219 220 // ââ Paso 4: Garantizar draw overlay como lastElementChild âââââââââ 221 const svg = document.getElementById('loteo-svg');
222 const overlay = document.getElementById('kpk-draw-overlay'); 223 if (svg && overlay && overlay !== svg.lastElementChild) { 224 svg.appendChild(overlay); 225 } 226 227 // ââ Paso 5: Marcar como sincronizado âââââââââââââââââââââââââââââ 228 window.DOMCache.lastSyncedVersion = window.DOMCache.version; 229 } 230 231 // âââ API PÃBLICA ââââââââââââââââââââââââââââââââââââââââââââââââââââ 232 window.FerrariSVGSync = { 233 syncSVGElements 234 }; 235 236 console.log('[Ferrari/SVGSync] â Módulo inicializado'); 237 238})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.