/* ============================================================
CLIENTS MANAGEMENT WITH MAP
============================================================ */
let map = null;
let clientMarkers = {};
let missingGeocodeClients = [];
let geocodeToastShown = false;
function initClientsTab() {
console.log(`initClientsTab: renderizando ${STATE.clients.length} clientes`);
initMap();
renderClientsTable();
resetClientForm();
if (map) {
setTimeout(() => map.invalidateSize(), 120);
}
}
function initMap() {
if (map) return;
const mapContainer = document.getElementById('clients-map');
if (!mapContainer) return;
// Initialize map
map = L.map('clients-map').setView(
[CONFIG.MAP.DEFAULT_LAT, CONFIG.MAP.DEFAULT_LNG],
CONFIG.MAP.DEFAULT_ZOOM
);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
}).addTo(map);
// Load client markers
loadClientMarkers();
}
async function loadClientMarkers() {
if (!map) return;
// Clear existing markers
Object.values(clientMarkers).forEach(marker => marker.remove());
clientMarkers = {};
missingGeocodeClients = [];
let firstRequest = true;
// Add markers for all clients
for (const client of STATE.clients) {
if (!client.address || client.id === 'c1') continue;
// Nominatim rate limit: 1 req/s; skip delay if result was cached
if (!firstRequest) {
const cacheKey = `geo_${client.address.toLowerCase()}`;
const cached = sessionStorage.getItem(cacheKey);
if (!cached) await (typeof sleep === 'function' ? sleep(1200) : new Promise(r => setTimeout(r, 1200)));
}
firstRequest = false;
const coords = await geocodeAddress(client.address);
if (!coords) {
missingGeocodeClients.push(client);
continue;
}
const marker = L.marker([coords.lat, coords.lng]).addTo(map)
.bindPopup(`
${escapeHtml(client.name)}
${escapeHtml(client.address)}
${client.phone || '—'}
`, { maxWidth: 250 });
clientMarkers[client.id] = marker;
}
renderPendingGeocodeList();
if (missingGeocodeClients.length > 0 && !geocodeToastShown) {
showToast(
`${missingGeocodeClients.length} dirección(es) no se pudieron ubicar en el mapa`,
'warning'
);
geocodeToastShown = true;
}
if (missingGeocodeClients.length === 0) {
geocodeToastShown = false;
}
}
function renderPendingGeocodeList() {
const panel = document.getElementById('clients-geocode-pending');
const list = document.getElementById('clients-geocode-pending-list');
if (!panel || !list) return;
if (!missingGeocodeClients.length) {
panel.classList.add('hidden');
list.innerHTML = '';
return;
}
panel.classList.remove('hidden');
list.innerHTML = missingGeocodeClients.map(client => `
${escapeHtml(client.name)}:
${escapeHtml(client.address || 'Sin dirección')}
`).join('');
}
function centerMapToClient(clientId) {
const client = STATE.clients.find(c => c.id === clientId);
if (!client || !map) return;
const marker = clientMarkers[clientId];
if (marker) {
map.setView(marker.getLatLng(), 15);
marker.openPopup();
} else if (client.address) {
geocodeAddress(client.address).then(coords => {
if (coords) {
map.setView([coords.lat, coords.lng], 15);
}
});
}
}
function selectClientOnMap(clientId) {
document.querySelectorAll('#admin-clients-tbody tr').forEach(tr => {
tr.classList.remove('selected');
});
const row = document.querySelector(`tr[data-client-id="${clientId}"]`);
if (row) row.classList.add('selected');
centerMapToClient(clientId);
editClientForm(clientId);
}
function openNewClientForm() {
resetClientForm();
switchAdminTab('clientes');
}
function deleteClientWithConfirm(clientId) {
if (clientId === 'c1') {
showToast('No se puede eliminar el cliente por defecto', 'error');
return;
}
const client = STATE.clients.find(c => c.id === clientId);
showConfirmDialog(
`¿Eliminar el cliente "${client?.name}"?`,
() => deleteClient(clientId)
);
}