// Clase base de los proveedores de ubicación: define el contrato común que // implementan todas las integraciones con backends de mapas (Google Maps, // Waze, OpenStreetMap, coordenadas sueltas y Nominatim como fallback de // texto). JS no tiene interfaces nativas, así que el contrato se documenta // aquí y se comprueba al registrar cada instancia (ver index.js). // // Contrato de cada proveedor: // - id: identificador estable del proveedor. Se copia al resultado para // que la UI pueda etiquetar el origen de la ubicación. // - detecta(entrada) → boolean: ¿esta entrada parece pertenecer a este // proveedor? Debe ser barato y síncrono (normalmente un regex). // - extrae(entrada) → Promise: extrae la información // disponible y devuelve { proveedor, lat, lng, ...extras } o null // si no se pudo extraer nada. Los errores de red se degradan a un // fallo controlado (null), nunca se lanzan al consumidor. // // El resultado es deliberadamente extensible: los consumidores leen solo los // campos que conocen (hoy lat/lng/nombre/provincia/direccion), de modo que // añadir campos nuevos en el futuro (teléfono, horarios…) no rompe nada. import { PROVINCIAS } from "../../constants/provincias.js"; export default class ProveedorUbicacion { // Identificador del proveedor; cada subclase debe sobrescribirlo. id = "proveedor"; // Nombres cooficiales (normalizados y sin artículo) → provincia canónica. // Nominatim puede devolver "Bizkaia", "Girona" o "Ourense" según el idioma // negociado; sin este mapa la provincia quedaría vacía innecesariamente. static ALIAS_PROVINCIAS = { bizkaia: "Vizcaya", gipuzkoa: "Guipúzcoa", araba: "Álava", girona: "Gerona", lleida: "Lérida", ourense: "Orense", coruna: "La Coruña", "illes balears": "Islas Baleares", asturies: "Asturias", }; // ¿Reconoce esta entrada? La base no reconoce nada. detecta(entrada) { return false; } // Extrae la ubicación de la entrada. Método "abstracto": la base lanza para // que una subclase registrada sin implementarlo falle rápido. async extrae(entrada) { throw new Error("sin implementar"); } // ── Helpers compartidos por todos los proveedores ──────────────────────── // Normaliza un texto: minúsculas, sin tildes y sin espacios extremos. Se usa // para comparar provincias y etiquetas sin depender de acentos. static normalizar(texto) { return (texto || "") .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .trim(); } normalizar(texto) { return ProveedorUbicacion.normalizar(texto); } // Empareja un texto libre contra el listado canónico de PROVINCIAS, // insensible a acentos y mayúsculas y tolerante con artículos ("Comunidad // de Madrid" → "Madrid", "A Coruña" → "La Coruña"). Devuelve "" si no casa // con ninguna, para que la UI fuerce la selección manual (mismo // comportamiento que tenía AutocompletarLocal). emparejarProvincia(texto) { const t = this.normalizar(texto); if (!t) return ""; const sinArticulo = (s) => s.replace(/^(el|la|los|las|a|o)\s+/, ""); const tSin = sinArticulo(t); const directa = PROVINCIAS.find((p) => { const np = ProveedorUbicacion.normalizar(p); const npSin = sinArticulo(np); return t === np || t.includes(np) || tSin === npSin || tSin.includes(npSin); }); if (directa) return directa; // Alias de nombres cooficiales: Nominatim puede responder en euskera, // catalán o gallego según el idioma negociado (D12 del diseño). return ProveedorUbicacion.ALIAS_PROVINCIAS[tSin] || ""; } // fetch a Nominatim con las cabeceras adecuadas y manejo de errores: si la // red o el servicio fallan devuelve null (fallo controlado) en vez de // lanzar, para que la pasarela nunca se rompa por un error de proveedor. async fetchNominatim(url) { try { const respuesta = await fetch(url, { headers: { "Accept-Language": "es", "User-Agent": "LocalesEspanoles/1.0" }, }); if (!respuesta.ok) return null; return await respuesta.json(); } catch { return null; } } }