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:
Lucas Tettamanti
2026-05-02 15:37:47 -03:00
parent aed79078de
commit c93955fa55
6 changed files with 219 additions and 394 deletions

View File

@@ -408,30 +408,6 @@ class SettingsCrud extends HTMLElement {
</div>
</div>
<!-- Delivery -->
<div class="panel">
<div class="panel-title">
<svg viewBox="0 0 24 24"><path d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"/></svg>
Delivery (Envío a domicilio)
</div>
<div class="toggle-row">
<div class="toggle ${s.delivery_enabled ? "active" : ""}" id="deliveryToggle"></div>
<span class="toggle-label">Delivery habilitado</span>
</div>
<div class="schedule-grid" id="deliverySchedule">
${this.renderScheduleGrid("delivery", s.delivery_enabled)}
</div>
<div class="min-order-field">
<div class="field">
<label>Pedido mínimo para delivery ($)</label>
<input type="number" id="deliveryMinOrder" value="${s.delivery_min_order || 0}" min="0" step="100" style="width:150px;" />
</div>
</div>
</div>
<!-- Retiro en tienda -->
<div class="panel">
<div class="panel-title">
@@ -490,13 +466,6 @@ class SettingsCrud extends HTMLElement {
}
setupEventListeners() {
// Toggle delivery
const deliveryToggle = this.shadowRoot.getElementById("deliveryToggle");
deliveryToggle?.addEventListener("click", () => {
this.settings.delivery_enabled = !this.settings.delivery_enabled;
this.render();
});
// Toggle pickup
const pickupToggle = this.shadowRoot.getElementById("pickupToggle");
pickupToggle?.addEventListener("click", () => {
@@ -673,23 +642,19 @@ class SettingsCrud extends HTMLElement {
}
collectScheduleFromInputs() {
const schedule = { delivery: {}, pickup: {} };
for (const type of ["delivery", "pickup"]) {
this.shadowRoot.querySelectorAll(`.hour-start[data-type="${type}"]`).forEach(input => {
const day = input.dataset.day;
const endInput = this.shadowRoot.querySelector(`.hour-end[data-type="${type}"][data-day="${day}"]`);
const toggle = this.shadowRoot.querySelector(`.day-toggle[data-type="${type}"][data-day="${day}"]`);
if (toggle?.classList.contains("active")) {
schedule[type][day] = {
start: input.value.trim() || (type === "delivery" ? "09:00" : "08:00"),
end: endInput?.value.trim() || (type === "delivery" ? "18:00" : "20:00"),
};
}
});
}
// Sólo pickup: el horario de delivery vive ahora dentro de cada zona.
const schedule = { pickup: {} };
this.shadowRoot.querySelectorAll(`.hour-start[data-type="pickup"]`).forEach((input) => {
const day = input.dataset.day;
const endInput = this.shadowRoot.querySelector(`.hour-end[data-type="pickup"][data-day="${day}"]`);
const toggle = this.shadowRoot.querySelector(`.day-toggle[data-type="pickup"][data-day="${day}"]`);
if (toggle?.classList.contains("active")) {
schedule.pickup[day] = {
start: input.value.trim() || "08:00",
end: endInput?.value.trim() || "20:00",
};
}
});
return schedule;
}
@@ -727,9 +692,7 @@ class SettingsCrud extends HTMLElement {
bot_name: this.shadowRoot.getElementById("botName")?.value || "",
store_address: this.shadowRoot.getElementById("storeAddress")?.value || "",
store_phone: this.shadowRoot.getElementById("storePhone")?.value || "",
delivery_enabled: this.settings.delivery_enabled,
pickup_enabled: this.settings.pickup_enabled,
delivery_min_order: parseFloat(this.shadowRoot.getElementById("deliveryMinOrder")?.value) || 0,
schedule,
delivery_zones,
};