Limpiar legacy delivery_* + arreglar carga del mapa en shadow DOM
Backend cleanup (todo el delivery vive ahora en delivery_zones.zones[]):
- Migration drop columns delivery_enabled / delivery_days / delivery_hours_start /
delivery_hours_end / delivery_min_order y limpieza de schedule.delivery JSONB.
- settingsRepo: SELECT/INSERT/UPDATE sólo con campos vigentes, formatScheduleHours
trabaja sobre pickup.
- handlers/settings: defaults sin legacy, validateSchedule sólo para pickup,
validateDeliveryZones nuevo (estructura GeoJSON + días).
- seed_piaf_settings_and_replies + tenant_settings migrations alineadas: schedule
sólo tiene pickup, delivery_zones queda en {} para reconfigurar via UI.
Frontend cleanup:
- settings-crud: borrado el panel "Delivery (Envío a domicilio)" + minOrder,
toggle deliveryEnabled, y el grid schedule.delivery. collectScheduleFromInputs
ahora sólo procesa pickup. save() ya no envía delivery_enabled/min_order.
Fix mapa (no cargaba):
- zone-map-editor: los <link> a leaflet.css/leaflet-geoman.css se inyectaban en
document.head, que NO cruza el shadow DOM de settings-crud, por lo que las
reglas de Leaflet no aplicaban al div del mapa. Ahora los <link> se anclan
como hijos del propio web component; al estar en light DOM dentro del shadow
root del padre, sí aplican.
- Espera explícita a que el stylesheet cargue antes de instanciar L.map.
- ResizeObserver + invalidateSize() para cuando el contenedor cambia tamaño
(router muestra/oculta panel, tabs, etc).
Smoke E2E sin regresión: 1kg vacío + envío → location en Centro → "martes 12hs"
→ orden confirmada con $28.000 (producto + envío). 157/157 tests verde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,46 +37,44 @@ const ZONE_PALETTE = [
|
||||
"--chart-orange", "--chart-pink", "--chart-gray",
|
||||
];
|
||||
|
||||
let _libsPromise = null;
|
||||
function ensureLeaflet() {
|
||||
let _scriptsPromise = null;
|
||||
function ensureLeafletScripts() {
|
||||
if (window.L && window.L.PM) return Promise.resolve();
|
||||
if (_libsPromise) return _libsPromise;
|
||||
_libsPromise = (async () => {
|
||||
if (!document.querySelector(`link[href="${LEAFLET_CSS}"]`)) {
|
||||
const l1 = document.createElement("link");
|
||||
l1.rel = "stylesheet"; l1.href = LEAFLET_CSS;
|
||||
document.head.appendChild(l1);
|
||||
}
|
||||
if (!document.querySelector(`link[href="${GEOMAN_CSS}"]`)) {
|
||||
const l2 = document.createElement("link");
|
||||
l2.rel = "stylesheet"; l2.href = GEOMAN_CSS;
|
||||
document.head.appendChild(l2);
|
||||
}
|
||||
if (!window.L) {
|
||||
await loadScript(LEAFLET_JS);
|
||||
}
|
||||
if (!window.L.PM) {
|
||||
await loadScript(GEOMAN_JS);
|
||||
}
|
||||
if (_scriptsPromise) return _scriptsPromise;
|
||||
_scriptsPromise = (async () => {
|
||||
if (!window.L) await loadScript(LEAFLET_JS);
|
||||
if (!window.L.PM) await loadScript(GEOMAN_JS);
|
||||
})();
|
||||
return _libsPromise;
|
||||
return _scriptsPromise;
|
||||
}
|
||||
|
||||
function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (document.querySelector(`script[src="${src}"]`)) {
|
||||
resolve();
|
||||
const existing = document.querySelector(`script[src="${src}"]`);
|
||||
if (existing) {
|
||||
if (existing.dataset.loaded === "1") { resolve(); return; }
|
||||
existing.addEventListener("load", () => resolve());
|
||||
existing.addEventListener("error", () => reject(new Error(`Failed to load ${src}`)));
|
||||
return;
|
||||
}
|
||||
const s = document.createElement("script");
|
||||
s.src = src;
|
||||
s.async = true;
|
||||
s.onload = () => resolve();
|
||||
s.onload = () => { s.dataset.loaded = "1"; resolve(); };
|
||||
s.onerror = () => reject(new Error(`Failed to load ${src}`));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForCSS(linkEl, timeoutMs = 4000) {
|
||||
if (linkEl.sheet) return Promise.resolve();
|
||||
return new Promise((resolve) => {
|
||||
const timer = setTimeout(resolve, timeoutMs);
|
||||
linkEl.addEventListener("load", () => { clearTimeout(timer); resolve(); }, { once: true });
|
||||
linkEl.addEventListener("error", () => { clearTimeout(timer); resolve(); }, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
function cssVar(name, fallback = "#0ea5e9") {
|
||||
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
return v || fallback;
|
||||
@@ -107,20 +105,48 @@ class ZoneMapEditor extends HTMLElement {
|
||||
this.style.position = "relative";
|
||||
this.style.width = "100%";
|
||||
this.style.height = this.getAttribute("height") || "480px";
|
||||
|
||||
// El web component vive en light DOM dentro del shadow root del padre.
|
||||
// Los <link> en document.head NO cruzan shadow boundaries, así que los
|
||||
// anclamos como hijos del propio elemento (sí cruzan, porque slot
|
||||
// composition trae nuestros children al árbol del padre con sus assets).
|
||||
const linkLeaflet = document.createElement("link");
|
||||
linkLeaflet.rel = "stylesheet";
|
||||
linkLeaflet.href = LEAFLET_CSS;
|
||||
this.appendChild(linkLeaflet);
|
||||
const linkGeoman = document.createElement("link");
|
||||
linkGeoman.rel = "stylesheet";
|
||||
linkGeoman.href = GEOMAN_CSS;
|
||||
this.appendChild(linkGeoman);
|
||||
|
||||
this._mapDiv = document.createElement("div");
|
||||
this._mapDiv.style.width = "100%";
|
||||
this._mapDiv.style.height = "100%";
|
||||
this._mapDiv.style.borderRadius = "var(--r-md, 10px)";
|
||||
this._mapDiv.style.overflow = "hidden";
|
||||
this._mapDiv.style.border = "1px solid var(--border, #e2e8f0)";
|
||||
this._mapDiv.style.background = "#e2e8f0";
|
||||
this.appendChild(this._mapDiv);
|
||||
|
||||
ensureLeaflet().then(() => this._initMap()).catch((err) => {
|
||||
this._mapDiv.innerHTML = `<div style="padding:16px;color:var(--err);font-family:var(--font-sans);">No se pudo cargar el mapa: ${err.message}</div>`;
|
||||
});
|
||||
ensureLeafletScripts()
|
||||
.then(() => waitForCSS(linkLeaflet))
|
||||
.then(() => this._initMap())
|
||||
.catch((err) => {
|
||||
this._mapDiv.innerHTML = `<div style="padding:16px;color:var(--err);font-family:var(--font-sans);">No se pudo cargar el mapa: ${err.message}</div>`;
|
||||
});
|
||||
|
||||
// Si el host estaba oculto al montar (router/visibility), Leaflet calcula
|
||||
// 0×0 y los tiles no se piden. Observamos el resize para recalcular.
|
||||
if (typeof ResizeObserver !== "undefined") {
|
||||
this._ro = new ResizeObserver(() => {
|
||||
if (this._map) this._map.invalidateSize();
|
||||
});
|
||||
this._ro.observe(this);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._ro) { this._ro.disconnect(); this._ro = null; }
|
||||
if (this._map) {
|
||||
this._map.remove();
|
||||
this._map = null;
|
||||
@@ -226,6 +252,10 @@ class ZoneMapEditor extends HTMLElement {
|
||||
|
||||
this._ready = true;
|
||||
this._renderLayers();
|
||||
// Forzar un invalidateSize después del primer paint por si el contenedor
|
||||
// recién obtuvo tamaño (panel oculto inicial / tabs / etc).
|
||||
requestAnimationFrame(() => this._map && this._map.invalidateSize());
|
||||
setTimeout(() => this._map && this._map.invalidateSize(), 250);
|
||||
}
|
||||
|
||||
_renderLayers() {
|
||||
|
||||
Reference in New Issue
Block a user