Se agregó las gráficas a Tramites #1
@ -45,8 +45,8 @@ const visibleMarkersCount = computed(() => {
|
||||
|
||||
// Coordenadas del centro de Comalcalco, Tabasco
|
||||
const COMALCALCO_CENTER = {
|
||||
lat: 18.2669,
|
||||
lng: -93.2072,
|
||||
lat: 18.26,
|
||||
lng: -93.25,
|
||||
};
|
||||
|
||||
// Límites expandidos de Tabasco (más amplios para capturar todas las obras)
|
||||
@ -60,6 +60,80 @@ const TABASCO_BOUNDS = {
|
||||
// Cache para geocodificación (evitar llamadas duplicadas)
|
||||
const geocodingCache = new Map();
|
||||
|
||||
const searchQuery = ref('');
|
||||
const searchResults = ref([]);
|
||||
const showSearchResults = ref(false);
|
||||
const searchLoading = ref(false);
|
||||
|
||||
const searchAddresses = async () => {
|
||||
if (!searchQuery.value.trim()) {
|
||||
searchResults.value = [];
|
||||
showSearchResults.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
searchLoading.value = true;
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
|
||||
// Buscar en las obras existentes
|
||||
const allObras = [
|
||||
...props.counters.sin_avances.detalles.map((obra) => ({
|
||||
...obra,
|
||||
estado: "sin_avances",
|
||||
})),
|
||||
...props.counters.abiertas.detalles.map((obra) => ({
|
||||
...obra,
|
||||
estado: "abiertas",
|
||||
})),
|
||||
...props.counters.finalizadas.detalles.map((obra) => ({
|
||||
...obra,
|
||||
estado: "finalizadas",
|
||||
})),
|
||||
];
|
||||
|
||||
// Filtrar obras que coincidan con la búsqueda
|
||||
const matches = allObras.filter(obra =>
|
||||
obra.direccion_obra?.toLowerCase().includes(query) ||
|
||||
obra.num_proyecto?.toLowerCase().includes(query)
|
||||
);
|
||||
|
||||
searchResults.value = matches.slice(0, 10); // Limitar a 10 resultados
|
||||
showSearchResults.value = true;
|
||||
searchLoading.value = false;
|
||||
};
|
||||
|
||||
//Función para ir a una obra específica
|
||||
const goToObra = async (obra) => {
|
||||
searchQuery.value = '';
|
||||
searchResults.value = [];
|
||||
showSearchResults.value = false;
|
||||
|
||||
// Buscar el marcador correspondiente
|
||||
const markerData = markers.value.find(m =>
|
||||
m.obras.some(o =>
|
||||
o.num_proyecto === obra.num_proyecto &&
|
||||
o.direccion_obra === obra.direccion_obra
|
||||
)
|
||||
);
|
||||
|
||||
if (markerData) {
|
||||
// Centrar el mapa en el marcador
|
||||
map.value.setCenter(markerData.marker.getPosition());
|
||||
map.value.setZoom(16);
|
||||
|
||||
// Abrir el InfoWindow
|
||||
markers.value.forEach((m) => m.infoWindow?.close());
|
||||
markerData.infoWindow.open(map.value, markerData.marker);
|
||||
}
|
||||
};
|
||||
|
||||
// Limpiar resultados de búsqueda
|
||||
const clearSearch = () => {
|
||||
searchQuery.value = '';
|
||||
searchResults.value = [];
|
||||
showSearchResults.value = false;
|
||||
};
|
||||
|
||||
const initMap = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
@ -68,7 +142,7 @@ const initMap = async () => {
|
||||
const loader = new Loader({
|
||||
apiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY,
|
||||
version: "weekly",
|
||||
libraries: ["places"],
|
||||
libraries: ["places", "geometry"],
|
||||
});
|
||||
|
||||
const google = await loader.load();
|
||||
@ -142,60 +216,77 @@ const loadMarkers = async () => {
|
||||
}))
|
||||
);
|
||||
|
||||
// Agrupar obras por dirección
|
||||
const obrasPorDireccion = new Map();
|
||||
// Agrupar obras por grupo
|
||||
const obrasPorGrupo = new Map();
|
||||
allObras.forEach(obra => {
|
||||
const direccion = obra.direccion_obra;
|
||||
if (!obrasPorDireccion.has(direccion)) {
|
||||
obrasPorDireccion.set(direccion, []);
|
||||
const clave = `${obra.direccion_obra}|${obra.estado}`;
|
||||
if(!obrasPorGrupo.has(clave)){
|
||||
obrasPorGrupo.set(clave, {
|
||||
direccion: obra.direccion_obra,
|
||||
estado: obra.estado,
|
||||
obras: []
|
||||
});
|
||||
}
|
||||
obrasPorDireccion.get(direccion).push(obra);
|
||||
obrasPorGrupo.get(clave).obras.push(obra);
|
||||
});
|
||||
|
||||
processingStats.value.total = allObras.length;
|
||||
|
||||
// Procesar grupos de obras por dirección en lotes
|
||||
const direcciones = Array.from(obrasPorDireccion.keys());
|
||||
const grupos = Array.from(obrasPorGrupo.values());
|
||||
const batchSize = 5;
|
||||
|
||||
for (let i = 0; i < direcciones.length; i += batchSize) {
|
||||
const batch = direcciones.slice(i, i + batchSize);
|
||||
const batchPromises = batch.map(direccion =>
|
||||
createMarkerForDireccion(direccion, obrasPorDireccion.get(direccion))
|
||||
for (let i = 0; i < grupos.length; i += batchSize) {
|
||||
const batch = grupos.slice(i, i + batchSize);
|
||||
const batchPromises = batch.map(grupo =>
|
||||
createMarkerForGroup(grupo)
|
||||
);
|
||||
|
||||
await Promise.allSettled(batchPromises);
|
||||
|
||||
if (i + batchSize < direcciones.length) {
|
||||
if (i + batchSize < grupos.length) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
updateMarkers();
|
||||
};
|
||||
|
||||
const createMarkerForDireccion = async (direccion, obrasEnDireccion) => {
|
||||
const createMarkerForGroup = async (grupo) => {
|
||||
const { direccion, obras: obrasEnGrupo } = grupo;
|
||||
if (!direccion || !geocoder.value) {
|
||||
obrasEnDireccion.forEach(() => processingStats.value.failed++);
|
||||
processingStats.value.failed += obrasEnGrupo.length;
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalización corregida de direcciones
|
||||
let direccionNormalizada = direccion
|
||||
.replace(/^RA\.\s*/i, "Ranchería ")
|
||||
.replace(/CIUDAD\s+/i, "")
|
||||
.replace(/2DA\s+SECCION/gi, "2 sección")
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/,\s*,/g, ",")
|
||||
.trim();
|
||||
|
||||
// Construir direcciones alternativas para geocodificación
|
||||
const addresses = [
|
||||
`${direccionNormalizada}, Comalcalco, Tabasco, México`,
|
||||
`${direccion}, Comalcalco, Tabasco, México`,
|
||||
`${direccion}, Tabasco, México`,
|
||||
`${direccion}, México`,
|
||||
`${direccionNormalizada.replace('Ranchería ', '')}, Comalcalco, Tabasco, México`,
|
||||
`${direccion.replace('RA. ', '')}, Comalcalco, Tabasco`,
|
||||
`Comalcalco, Tabasco, México`
|
||||
];
|
||||
|
||||
// Verificar cache primero
|
||||
const cacheKey = direccion;
|
||||
if (geocodingCache.has(cacheKey)) {
|
||||
const cachedData = geocodingCache.get(cacheKey);
|
||||
processingStats.value.duplicates += obrasEnDireccion.length - 1;
|
||||
return createMarkerFromPosition(obrasEnDireccion, cachedData.position, cachedData.formatted_address);
|
||||
processingStats.value.duplicates += obrasEnGrupo.length - 1;
|
||||
return createMarkerFromPosition(grupo, cachedData.position, cachedData.formatted_address);
|
||||
}
|
||||
|
||||
// Intentar geocodificación con direcciones alternativas
|
||||
for (const address of addresses) {
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const address = addresses[i];
|
||||
try {
|
||||
const result = await geocodeAddress(address);
|
||||
if (result.success) {
|
||||
@ -205,16 +296,16 @@ const createMarkerForDireccion = async (direccion, obrasEnDireccion) => {
|
||||
formatted_address: result.formatted_address,
|
||||
});
|
||||
|
||||
return createMarkerFromPosition(obrasEnDireccion, result.position, result.formatted_address);
|
||||
return createMarkerFromPosition(grupo, result.position, result.formatted_address);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Error geocodificando ${address}:`, err);
|
||||
console.warn(`Error en intento ${i + 1}: ${address}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Si todas las direcciones fallan, crear marcador fallback
|
||||
processingStats.value.failed += obrasEnDireccion.length;
|
||||
|
||||
processingStats.value.failed += obrasEnGrupo.length;
|
||||
// Crear marcador fallback con offset aleatorio
|
||||
const randomOffset = {
|
||||
lat: (Math.random() - 0.5) * 0.02,
|
||||
lng: (Math.random() - 0.5) * 0.02,
|
||||
@ -225,23 +316,66 @@ const createMarkerForDireccion = async (direccion, obrasEnDireccion) => {
|
||||
lng: COMALCALCO_CENTER.lng + randomOffset.lng,
|
||||
};
|
||||
|
||||
return createMarkerFromPosition(obrasEnDireccion, fallbackPosition, "Ubicación aproximada - Comalcalco, Tabasco");
|
||||
return createMarkerFromPosition(grupo, fallbackPosition, "Ubicación aproximada - Comalcalco, Tabasco");
|
||||
};
|
||||
|
||||
const geocodeAddress = (address) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
geocoder.value.geocode({ address }, (results, status) => {
|
||||
if (status === "OK" && results[0]) {
|
||||
const position = results[0].geometry.location;
|
||||
const lat = position.lat();
|
||||
const lng = position.lng();
|
||||
return new Promise((resolve) => {
|
||||
const request = {
|
||||
address: address,
|
||||
region: 'mx',
|
||||
bounds: new google.maps.LatLngBounds(
|
||||
new google.maps.LatLng(18.1, -93.4),
|
||||
new google.maps.LatLng(18.4, -93.0)
|
||||
),
|
||||
};
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
position: { lat, lng },
|
||||
formatted_address: results[0].formatted_address,
|
||||
});
|
||||
geocoder.value.geocode(request, (results, status) => {
|
||||
if (status === "OK" && results && results.length > 0) {
|
||||
let bestResult = null;
|
||||
|
||||
for (let result of results) {
|
||||
const formatted = result.formatted_address.toLowerCase();
|
||||
|
||||
// Priorizar resultados que mencionen Comalcalco
|
||||
if (formatted.includes('comalcalco')) {
|
||||
bestResult = result;
|
||||
break;
|
||||
}
|
||||
|
||||
// Si no encuentra Comalcalco, verificar distancia
|
||||
if (!bestResult) {
|
||||
const position = result.geometry.location;
|
||||
const comalcalcoCenter = new google.maps.LatLng(COMALCALCO_CENTER.lat, COMALCALCO_CENTER.lng);
|
||||
const distance = google.maps.geometry.spherical.computeDistanceBetween(position, comalcalcoCenter);
|
||||
|
||||
// Si está dentro de 50km de Comalcalco, considerarlo válido
|
||||
if (distance <= 50000) {
|
||||
bestResult = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestResult) {
|
||||
const position = bestResult.geometry.location;
|
||||
const lat = position.lat();
|
||||
const lng = position.lng();
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
position: { lat, lng },
|
||||
formatted_address: bestResult.formatted_address,
|
||||
});
|
||||
} else {
|
||||
console.warn(`No se encontró resultado válido para "${address}"`);
|
||||
resolve({
|
||||
success: false,
|
||||
status: 'NO_VALID_RESULT',
|
||||
address,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn(`Geocodificación falló para "${address}": ${status}`);
|
||||
resolve({
|
||||
success: false,
|
||||
status,
|
||||
@ -252,19 +386,18 @@ const geocodeAddress = (address) => {
|
||||
});
|
||||
};
|
||||
|
||||
const createMarkerFromPosition = (obrasEnPosicion, position, formatted_address) => {
|
||||
const lat = position.lat;
|
||||
const lng = position.lng;
|
||||
const createMarkerFromPosition = (grupo, position, formatted_address) => {
|
||||
const { obras: obrasEnPosicion, estado } = grupo;
|
||||
|
||||
// Verificar límites expandidos
|
||||
if (
|
||||
lat < TABASCO_BOUNDS.south ||
|
||||
lat > TABASCO_BOUNDS.north ||
|
||||
lng < TABASCO_BOUNDS.west ||
|
||||
lng > TABASCO_BOUNDS.east
|
||||
) {
|
||||
processingStats.value.outOfBounds += obrasEnPosicion.length;
|
||||
}
|
||||
const statusOffsets = {
|
||||
sin_avances: { lat: 0.00008, lng: -0.00005 }, // Arriba-Izquierda
|
||||
abiertas: { lat: 0, lng: 0.0001 }, // Derecha
|
||||
finalizadas: { lat: -0.00004,lng: -0.00005 }, // Abajo-Izquierda
|
||||
};
|
||||
|
||||
const offset = statusOffsets[estado] || { lat: 0, lng: 0 };
|
||||
const lat = position.lat + offset.lat;
|
||||
const lng = position.lng + offset.lng;
|
||||
|
||||
// Usar la primera obra para definir el marcador principal
|
||||
const obraPrincipal = obrasEnPosicion[0];
|
||||
@ -272,11 +405,11 @@ const createMarkerFromPosition = (obrasEnPosicion, position, formatted_address)
|
||||
const marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(lat, lng),
|
||||
map: map.value,
|
||||
title: obrasEnPosicion.length === 1
|
||||
? `${obraPrincipal.num_proyecto} - ${obraPrincipal.direccion_obra}`
|
||||
: `${obrasEnPosicion.length} obras en ${obraPrincipal.direccion_obra}`,
|
||||
icon: createCustomMarker(obraPrincipal.estado),
|
||||
visible: filters[obraPrincipal.estado],
|
||||
title: obrasEnPosicion.length > 1
|
||||
? `${obrasEnPosicion.length} obras en ${obraPrincipal.direccion_obra} (${estado.replace('_', ' ')})`
|
||||
: `${obraPrincipal.num_proyecto} - ${obraPrincipal.direccion_obra}`,
|
||||
icon: createCustomMarker(estado), // El color del marcador depende del estado del grupo.
|
||||
visible: filters[estado],
|
||||
animation: google.maps.Animation.DROP,
|
||||
});
|
||||
|
||||
@ -304,75 +437,17 @@ const createMarkerFromPosition = (obrasEnPosicion, position, formatted_address)
|
||||
|
||||
const createCustomMarker = (estado) => {
|
||||
const color = markerColors[estado];
|
||||
const width = 32;
|
||||
const height = 48;
|
||||
|
||||
return {
|
||||
return {
|
||||
url: `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(`
|
||||
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<!-- Sombra para el marcador -->
|
||||
<filter id="dropshadow-${estado}" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="rgba(0,0,0,0.3)" flood-opacity="0.5"/>
|
||||
</filter>
|
||||
|
||||
<!-- Gradiente para dar volumen -->
|
||||
<radialGradient id="gradient-${estado}" cx="30%" cy="20%" r="70%">
|
||||
<stop offset="0%" style="stop-color:white;stop-opacity:0.3" />
|
||||
<stop offset="100%" style="stop-color:${color};stop-opacity:1" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Forma principal del marcador (gota/pin) -->
|
||||
<path d="M ${width / 2} ${height - 4}
|
||||
C ${width / 2} ${height - 4},
|
||||
${width * 0.125} ${height * 0.58},
|
||||
${width * 0.125} ${height * 0.375}
|
||||
C ${width * 0.125} ${height * 0.167},
|
||||
${width * 0.292} 2,
|
||||
${width / 2} 2
|
||||
C ${width * 0.708} 2,
|
||||
${width * 0.875} ${height * 0.167},
|
||||
${width * 0.875} ${height * 0.375}
|
||||
C ${width * 0.875} ${height * 0.58},
|
||||
${width / 2} ${height - 4},
|
||||
${width / 2} ${height - 4} Z"
|
||||
fill="url(#gradient-${estado})"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
filter="url(#dropshadow-${estado})"/>
|
||||
|
||||
<!-- Borde interior más oscuro -->
|
||||
<path d="M ${width / 2} ${height - 4}
|
||||
C ${width / 2} ${height - 4},
|
||||
${width * 0.125} ${height * 0.58},
|
||||
${width * 0.125} ${height * 0.375}
|
||||
C ${width * 0.125} ${height * 0.167},
|
||||
${width * 0.292} 2,
|
||||
${width / 2} 2
|
||||
C ${width * 0.708} 2,
|
||||
${width * 0.875} ${height * 0.167},
|
||||
${width * 0.875} ${height * 0.375}
|
||||
C ${width * 0.875} ${height * 0.58},
|
||||
${width / 2} ${height - 4},
|
||||
${width / 2} ${height - 4} Z"
|
||||
fill="none"
|
||||
stroke="${color}"
|
||||
stroke-width="1"
|
||||
opacity="0.8"/>
|
||||
|
||||
<!-- Círculo interior blanco -->
|
||||
<circle cx="${width / 2}"
|
||||
cy="${height * 0.375}"
|
||||
r="${width * 0.25}"
|
||||
fill="white"
|
||||
stroke="${color}"
|
||||
stroke-width="1"/>
|
||||
<svg width="24" height="36" viewBox="0 0 24 36" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 0C5.373 0 0 5.373 0 12c0 9 12 24 12 24s12-15 12-24c0-6.627-5.373-12-12-12z" fill="${color}"/>
|
||||
<circle cx="12" cy="12" r="6" fill="white"/>
|
||||
<circle cx="12" cy="12" r="3" fill="${color}"/>
|
||||
</svg>
|
||||
`)}`,
|
||||
scaledSize: new google.maps.Size(width, height),
|
||||
anchor: new google.maps.Point(width / 2, height - 4),
|
||||
origin: new google.maps.Point(0, 0),
|
||||
scaledSize: new google.maps.Size(24, 36),
|
||||
anchor: new google.maps.Point(12, 36)
|
||||
};
|
||||
};
|
||||
|
||||
@ -571,6 +646,122 @@ onMounted(() => {
|
||||
|
||||
<!-- Filtros por estado -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- NUEVO: Buscador de direcciones -->
|
||||
<div class="relative">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
@input="searchAddresses"
|
||||
@focus="searchAddresses"
|
||||
type="text"
|
||||
placeholder="Buscar obra o dirección..."
|
||||
class="w-64 px-3 py-1 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<svg
|
||||
v-if="searchLoading"
|
||||
class="animate-spin h-4 w-4 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
v-else-if="searchQuery"
|
||||
@click="clearSearch"
|
||||
class="h-4 w-4 text-gray-400 cursor-pointer hover:text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
class="h-4 w-4 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NUEVO: Resultados de búsqueda -->
|
||||
<div
|
||||
v-if="showSearchResults && searchResults.length > 0"
|
||||
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
v-for="obra in searchResults"
|
||||
:key="`${obra.num_proyecto}-${obra.direccion_obra}`"
|
||||
@click="goToObra(obra)"
|
||||
class="px-3 py-2 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">
|
||||
{{ obra.num_proyecto || 'N/A' }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 truncate">
|
||||
{{ obra.direccion_obra }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-shrink-0 ml-2">
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="{
|
||||
'bg-red-100 text-red-800': obra.estado === 'sin_avances',
|
||||
'bg-yellow-100 text-yellow-800': obra.estado === 'abiertas',
|
||||
'bg-green-100 text-green-800': obra.estado === 'finalizadas'
|
||||
}"
|
||||
>
|
||||
{{
|
||||
obra.estado === 'sin_avances' ? 'Sin avances' :
|
||||
obra.estado === 'abiertas' ? 'Abierta' : 'Finalizada'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NUEVO: Mensaje cuando no hay resultados -->
|
||||
<div
|
||||
v-else-if="showSearchResults && searchResults.length === 0 && searchQuery.trim()"
|
||||
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg"
|
||||
>
|
||||
<div class="px-3 py-2 text-sm text-gray-500 text-center">
|
||||
No se encontraron resultados
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<input
|
||||
id="sin-avances"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user