🛰 GEE Multisensor v1.0
1
Fundamentos
2
Tu ROI
3
Sentinel-2
4
ASTER
5
Landsat
6
Fusión + Campo
7
Anexo: Caso Au
Módulo 1 de 6 — Fundamentos

Por qué combinar ASTER + Sentinel-2 + Landsat

Ningún sensor por sí solo resuelve el mapeo de alteración hidrotermal. Cada uno aporta una pieza distinta del rompecabezas: resolución espacial, resolución espectral SWIR, o profundidad histórica. Este curso te enseña a usar los tres de forma complementaria — y sobre la zona que tú elijas, no sobre una coordenada fija.

📡
La física detrás del cociente de bandas
Los óxidos de hierro (hematita, goethita, jarosita) reflejan fuertemente en rojo/NIR y absorben en azul, con una caída característica cerca de 850–900 nm. Los minerales de arcilla y sulfatos (caolinita, illita, sericita, alunita) tienen una firma de absorción del enlace Al–OH cerca de 2160–2220 nm, dentro del SWIR. Un índice de cociente Banda-A/Banda-B amplifica ese contraste: si el mineral objetivo absorbe en B pero no en A, el cociente sube por encima de 1.0.
Tabla comparativa — fortalezas y límites de cada sensor
SensorResoluciónBandas clave (óxidos/arcillas)RevisitaCobertura temporalMejor uso
Sentinel-2 10–20 m B2, B4, B8, B11, B12 ~5 días 2015 — presente Barrido rápido, alta frecuencia, detalle local
ASTER 15–30–90 m VNIR B1–B3, SWIR B4–B9, TIR B10–B14 Programada (no sistemática) 2000 — presente (VNIR) · SWIR solo hasta 2008 Mineralogía fina: diferencia alunita, caolinita, sericita
Landsat 5/7 30 m TM/ETM+ B1,B3,B4,B5,B7 16 días 1984 — 2012 / 2022 Contexto histórico, cambios en décadas pasadas
Landsat 8/9 30 m OLI SR_B2,SR_B4,SR_B5,SR_B6,SR_B7 + TIR 16 días (8 combinados) 2013 — presente Cobertura regional grande, sin fraccionar en tiles
Bandas equivalentes entre sensores
Región espectralSentinel-2Landsat 8/9 OLILandsat 5/7 TM/ETM+ASTER
AzulB2 (0.49 μm)SR_B2 (0.48 μm)B1 (0.48 μm)B1 (0.56 μm)*
RojoB4 (0.66 μm)SR_B4 (0.65 μm)B3 (0.66 μm)B2 (0.66 μm)
NIRB8 (0.84 μm)SR_B5 (0.86 μm)B4 (0.83 μm)B3N (0.81 μm)
SWIR 1B11 (1.61 μm)SR_B6 (1.61 μm)B5 (1.65 μm)B4 (1.65 μm)
SWIR 2B12 (2.19 μm)SR_B7 (2.20 μm)B7 (2.22 μm)B5–B9 (2.15–2.43 μm, 5 bandas)

* ASTER B1 es en realidad verde/rojo (0.52–0.60 μm); se ubica aquí solo como referencia de posición relativa dentro del VNIR.

Zonación hidrotermal típica — lo que cada índice te va a mostrar
🔴 Núcleo silíceo / oxidado
Cuarzo · alunita
jarosita · hematita
gossans
→
🟠 Halo argílico
Caolinita · sericita
illita · esmectita
arcillas Al–OH
→
🟢 Halo propilítico distal
Clorita · epidota
calcita · carbonatos
🧭
La estrategia de este curso
Módulo 2: defines tu ROI de forma paramétrica — cualquier punto, rectángulo, polígono o asset del mundo. Módulo 3: Sentinel-2 hace un barrido rápido de óxidos y arcillas en general. Módulo 4: ASTER, cuando cubre tu zona, discrimina qué arcilla específica hay. Módulo 5: Landsat aporta profundidad histórica y cubre áreas grandes sin fraccionar. Módulo 6: fusionas todo en un mapa de prioridades para campo. No hay coordenadas fijas en ningún módulo — todo cuelga de un único objeto PARAMS que defines una vez.
Módulo 1 de 6
Módulo 2 de 6 — Configuración espacial

Tu Área de Interés — para cualquier zona del mundo

Este es el módulo que hace que el curso funcione en cualquier lugar. En vez de un punto fijo grabado en el código, el ROI se construye a partir de un parámetro que tú cambias — el resto del script no se toca.

Cuatro formas de definir tu ROI
📍
Punto + buffer
Da un [lon, lat] y un radio en metros. Ideal cuando ya tienes una coordenada de interés (una mina, un prospecto, una muestra con anomalía) y quieres una ventana de trabajo alrededor.
▭
Rectángulo (bounding box)
Da [lonMin, latMin, lonMax, latMax]. Rápido para exploración regional cuando ya conoces los límites aproximados de tu concesión o cuenca de interés.
✏
Polígono dibujado a mano
Usa las herramientas de dibujo del panel de Geometry Imports del Code Editor de GEE para trazar un contorno irregular (una cuenca, un distrito minero). El polígono queda disponible como variable geometry.
🗂
Asset importado
Sube tu propio shapefile o KML a GEE Assets (Assets → New → Shape files) y cárgalo como FeatureCollection. Es la opción más robusta si ya trabajas con polígonos en QGIS/ArcGIS.
El objeto PARAMS — un solo lugar para cambiar de zona

Declara todo lo configurable en un único objeto al inicio del script. Cambiar de Potosí a cualquier otro distrito minero del planeta significa editar estas líneas — nada más.

GEE JavaScript — ConfiguraciónPARAMS + selección de ROI
var PARAMS = {
  // ── ROI: elige 'point' | 'rectangle' | 'polygon' | 'asset' ──
  roiType     : 'point',

  // se usa solo si roiType = 'point' (ejemplo: reemplaza por tu zona)
  point       : [-65.75, -19.58],
  bufferM     : 5000,

  // se usa solo si roiType = 'rectangle'
  rectangle   : [-66.00, -19.80, -65.50, -19.40],

  // se usa solo si roiType = 'asset' (tu FeatureCollection en Assets)
  assetId     : 'users/tu_usuario/mi_area_interes',

  // ── ventana temporal y calidad de escena ──
  dateStart   : '2023-01-01',
  dateEnd     : '2023-12-31',
  maxCloud    : 20
};

// ── construcción del ROI a partir del PARAMS ──
var roi;
if (PARAMS.roiType === 'point') {
  roi = ee.Geometry.Point(PARAMS.point).buffer(PARAMS.bufferM).bounds();
} else if (PARAMS.roiType === 'rectangle') {
  roi = ee.Geometry.Rectangle(PARAMS.rectangle);
} else if (PARAMS.roiType === 'polygon') {
  roi = geometry; // dibujado con las herramientas del Code Editor
} else {
  roi = ee.FeatureCollection(PARAMS.assetId).geometry();
}

Map.centerObject(roi, 10);
Map.addLayer(roi, {color: 'yellow'}, 'ROI');
print('Área del ROI (km²):', roi.area().divide(1e6));

Todos los módulos siguientes reutilizan esta misma variable roi y este mismo objeto PARAMS — no vuelvas a escribir coordenadas en ningún otro bloque de código.

⚠
Cuidado con el tamaño del ROI
Cuanto mayor el área, mayor el tiempo de cómputo y el riesgo de exceder los límites de memoria interactiva de GEE. Para análisis exploratorio en el mapa, manténte por debajo de ~5000 km². Para áreas mayores, usa bestEffort: true y tileScale en los reduceRegion, y exporta con Export.image.toDrive (que sí procesa en el backend sin ese límite) en vez de intentar visualizar todo interactivamente.
💾
Reutiliza tu ROI entre sesiones
La primera vez que definas un polígono a mano, expórtalo como Asset propio (Export.table.toAsset). Así, en tu próxima sesión solo cambias PARAMS.roiType a 'asset' y apuntas al Asset guardado — sin redibujar nada.
Módulo 2 de 6
Módulo 3 de 6 — Barrido rápido

Sentinel-2 — óxidos y arcillas en alta frecuencia

10–20 m de resolución y revisita cada ~5 días hacen de Sentinel-2 el mejor punto de partida para cualquier zona: cobertura garantizada desde 2015 y actualización constante, sin depender de programación de escenas.

Pipeline del módulo
1
Cargar colección y filtrar
COPERNICUS/S2_SR_HARMONIZED, recortada por roi, por la ventana de fechas de PARAMS y por porcentaje de nubes.
2
Compuesto mediana
La mediana temporal elimina nubes residuales y sombras que sobreviven al filtro de metadatos.
3
Máscara de vegetación (NDVI)
La vegetación densa oculta la firma espectral de la roca. Se excluyen píxeles con NDVI alto antes de interpretar los índices minerales.
4
Calcular índices de cociente
Óxidos de Fe, minerales ferrosos y arcillas Al–OH (sin discriminar cuál arcilla — eso lo resuelve ASTER en el Módulo 4).
GEE JavaScript — Sentinel-2Carga y compuesto
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(roi)
  .filterDate(PARAMS.dateStart, PARAMS.dateEnd)
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', PARAMS.maxCloud))
  .median()
  .clip(roi);

var ndvi = s2.normalizedDifference(['B8', 'B4']).rename('NDVI');
var rockMask = ndvi.lt(0.25); // excluye vegetación densa
GEE JavaScript — Sentinel-2Índices de óxidos y arcillas
var feOxideS2  = s2.select('B4').divide(s2.select('B2')).rename('Fe_oxido').updateMask(rockMask);
var ferrosoS2  = s2.select('B11').divide(s2.select('B8')).rename('Ferroso').updateMask(rockMask);
var arcillaS2  = s2.select('B11').divide(s2.select('B12')).rename('Arcilla_OH').updateMask(rockMask);

Map.addLayer(arcillaS2, {min:0.9,max:1.4,palette:['000000','ffff00','ff0000']}, 'Arcillas (Sentinel-2)');
Tabla de índices — Sentinel-2
ÍndiceFórmulaQué detecta
Óxidos de Fe
Sabins 1999
B4 / B2 Hematita, goethita, jarosita — gossans y halos de oxidación superficial.
Minerales ferrosos B11 / B8 Fe²⁺ en silicatos máficos (biotita, clorita, anfíboles alterados).
Arcillas Al–OH (general)
Crósta & Moore 1989, adaptado
B11 / B12 Presencia conjunta de caolinita/illita/sericita/alunita — sin discriminar cuál.
NDVI (máscara) (B8−B4)/(B8+B4) Excluye vegetación que enmascara la señal mineral de la roca expuesta.
🔍
El límite de Sentinel-2: solo dos bandas SWIR
B11 y B12 son anchas y solo dos, así que el índice de arcillas es un indicador general — te dice "aquí hay arcilla" pero no "aquí hay caolinita y no sericita". Si necesitas esa discriminación fina, es el trabajo del Módulo 4 (ASTER), que tiene 5 bandas SWIR angostas.
Módulo 3 de 6
Módulo 4 de 6 — Mineralogía fina

ASTER — discriminación mineral detallada vía SWIR

Donde Sentinel-2 dice "hay arcilla", ASTER dice cuál. Sus 5 bandas SWIR angostas (B4–B9) permiten separar alunita, caolinita y sericita — minerales que marcan zonas distintas del sistema hidrotermal.

⚠
¿Por qué filtrar hasta abril de 2008?
El detector SWIR de ASTER (bandas B4–B9) falló en abril de 2008 por un mal funcionamiento del sistema de enfriamiento criogénico. Sin estas bandas no hay alunita, sericita ni caolinita distinguibles. Las bandas VNIR (B1–B3) siguen funcionando hoy, así que úsalas para óxidos de Fe o para true-color; para los índices SWIR de este módulo, limita la fecha de fin a '2008-04-28'.
GEE JavaScript — ASTERCarga y compuesto (ventana SWIR válida)
var aster = ee.ImageCollection('ASTER/AST_L1T_003')
  .filterBounds(roi)
  .filterDate('2000-01-01', '2008-04-28') // límite real del SWIR
  .filter(ee.Filter.lt('CLOUDCOVER', PARAMS.maxCloud))
  .median()
  .clip(roi);

print('Escenas ASTER SWIR encontradas en el ROI:',
  ee.ImageCollection('ASTER/AST_L1T_003').filterBounds(roi).filterDate('2000-01-01','2008-04-28').size());
// ← si esto es 0, tu zona no tiene cobertura ASTER SWIR: ver el aviso del Módulo 6
Tabla completa de índices mineralógicos
Mineral objetivoFórmulaFísica del índice
Alunita (AAS)
Rowan et al. 2003 — Cuprite
B7 / B5 B5 (2.17 μm) absorbe por el enlace Al–OH de alunita; B7 (2.26 μm) refleja. Diagnóstico de alteración argílica avanzada.
Caolinita
Ninomiya 2002
B6 / B5 Doble absorción Al–OH en 2.17 μm (B5) y 2.21 μm (B6); B5 absorbe más fuerte, cociente > 1 marca caolinita.
Sericita / illita
Ninomiya 2002
B5 / B6 Absorción de sericita en ~2.20 μm (B5); B6 (~2.21 μm) es zona de reflectancia relativa.
Grupos OH (halo distal)
Rowan & Mars 2003
(B6+B9)/(B8×2) B6 y B9 son zonas de reflectancia; B8 (~2.33 μm) absorbe en carbonatos e illita.
Propilítica / clorita
Corbett & Leach 1998
B4 / B5 Clorita y epidota reflejan en B4 (1.65 μm) y absorben en B5 (2.17 μm) — indica la zona más distal del sistema.
Óxidos de Fe (VNIR)
Sabins 1999
B2 / B1 Goethita y hematita absorben en azul-verde (B1) y reflejan en rojo (B2). Sigue funcionando después de 2008.
Sílice (TIR)
Ninomiya 2003
(B13/B12)+(B13/B14) Restrahlen feature del cuarzo: alta emisividad en B13 (~10.6 μm) relativa a B12 y B14. Resolución nativa 90 m.
Carbonatos (TIR)
Ninomiya 2003
(B13+B11)/(B12×2) Calcita y dolomita: alta emisividad en B11 y B13 respecto a B12. Marca halos distales de carbonato.
GEE JavaScript — ASTERCálculo de los índices principales
var alunitaIndex   = aster.select('B07').divide(aster.select('B05')).rename('Alunita');
var caolinitaIndex = aster.select('B06').divide(aster.select('B05')).rename('Caolinita');
var sericitaIndex = aster.select('B05').divide(aster.select('B06')).rename('Sericita');
var oxidosVnirIndex = aster.select('B02').divide(aster.select('B01')).rename('Oxidos_VNIR');
var siliceTirIndex = aster.select('B13').divide(aster.select('B12'))
  .add(aster.select('B13').divide(aster.select('B14')))
  .rename('Silice_TIR'); // requerido por la fusión del Módulo 6 — antes faltaba esta línea
Módulo 4 de 6
Módulo 5 de 6 — Contexto histórico y regional

Landsat — décadas de historia y grandes áreas sin fraccionar

Cuando tu ROI cubre miles de km² o necesitas ver cómo cambió una zona en 20-30 años, Landsat es la herramienta correcta: la misma familia de sensores cubre el planeta de forma continua desde 1984.

MisiónPeriodoNotas
Landsat 5 (TM)1984 – 2012Numeración de bandas distinta a OLI: B1 azul, B3 rojo, B4 NIR, B5 SWIR1, B7 SWIR2.
Landsat 7 (ETM+)1999 – 2022Falla del SLC en 2003 introduce franjas sin datos ("gaps") en escenas posteriores.
Landsat 8 (OLI/TIRS)2013 – presenteNumeración SR_B1…SR_B7 + banda térmica ST_B10.
Landsat 9 (OLI-2)2021 – presenteMismo esquema de bandas que Landsat 8; se pueden combinar en una sola colección.
GEE JavaScript — Landsat 8/9Carga con corrección de factores de escala
function aplicarFactoresEscala(img) {
  var optico = img.select('SR_B.').multiply(0.0000275).add(-0.2);
  var termico = img.select('ST_B10').multiply(0.00341802).add(149.0);
  return img.addBands(optico, null, true).addBands(termico, null, true);
}

var landsat = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
  .merge(ee.ImageCollection('LANDSAT/LC09/C02/T1_L2'))
  .filterBounds(roi)
  .filterDate(PARAMS.dateStart, PARAMS.dateEnd)
  .filter(ee.Filter.lt('CLOUD_COVER', PARAMS.maxCloud))
  .map(aplicarFactoresEscala)
  .median()
  .clip(roi);

var feOxideL8 = landsat.select('SR_B4').divide(landsat.select('SR_B2')).rename('Fe_oxido_L8');
var arcillaL8  = landsat.select('SR_B6').divide(landsat.select('SR_B7')).rename('Arcilla_L8');
🕰
Si tu zona necesita historia previa a 2013
Sustituye la colección por 'LANDSAT/LT05/C02/T1_L2' (Landsat 5, hasta 2012) o 'LANDSAT/LE07/C02/T1_L2' (Landsat 7). Los mismos índices se recalculan usando SR_B3/SR_B1 (óxidos) y SR_B5/SR_B7 (arcillas) — la física es idéntica, solo cambia el número de banda.
📈
Uso multitemporal — comparar décadas
Ejecuta el mismo bloque de código dos veces con distintos PARAMS.dateStart/dateEnd (ej. 1990 vs. 2023) sobre el mismo roi y resta los índices resultantes. Es la forma más directa de detectar nueva exposición de roca por erosión, movimientos de tierra, o expansión de drenaje ácido de minas (AMD) a lo largo del tiempo.
Módulo 5 de 6
Módulo 6 de 6 — Integración final

Fusión multisensor, priorización y exportación

Aquí combinas los índices de los tres sensores en un único mapa de prospectividad, y lo conviertes en targets concretos y capas listas para GIS — sin importar qué zona hayas elegido en el Módulo 2.

Paso 1 — Normalizar cada índice al mismo rango [0,1]

Cada índice tiene una escala distinta (unos van de 0.8 a 1.6, otros de 1 a 40). Antes de sumarlos hay que llevarlos a un rango común usando percentiles robustos P5–P95, que ignoran outliers extremos (nubes residuales, píxeles corruptos).

GEE JavaScript — FusiónNormalización robusta
var normalize01 = function(img) {
  var stats = img.reduceRegion({
    reducer: ee.Reducer.percentile([5, 95]),
    geometry: roi, scale: 30, bestEffort: true
  });
  var minVal = ee.Number(stats.values().get(0));
  var maxVal = ee.Number(stats.values().get(1));
  var safeMax = ee.Algorithms.If(maxVal.subtract(minVal).gt(1e-10), maxVal, minVal.add(1));
  return img.subtract(minVal).divide(ee.Number(safeMax).subtract(minVal)).clamp(0, 1);
};
Paso 2 — Ponderar y combinar
Modelo completo — cuando ASTER SWIR cubre tu zona
Alunita/Caolinita/Sericita (ASTER)
30%
Arcillas Al–OH (Sentinel-2)
25%
Óxidos de Fe (S2 + Landsat)
20%
Sílice / carbonatos TIR (ASTER)
15%
Persistencia histórica (Landsat)
10%
Total100%
🧩
Si tu zona no tiene cobertura ASTER SWIR utilizable
Pasa esto en zonas fuera de la huella de escenas ASTER pre-2008, o con nubosidad persistente en esas fechas. La solución: redistribuye el 45% que ocupaba ASTER (30%+15%) hacia Sentinel-2 y Landsat, quedando Arcillas Al–OH 45% · Óxidos de Fe 35% · Persistencia histórica 20%. El modelo sigue siendo válido — pierde capacidad de discriminar el mineral de arcilla exacto, pero conserva la detección de zonas anómalas.
⚠
Si solo ejecutaste hasta aquí y no viste nada en el mapa, es normal
El bloque de la versión anterior calculaba prospectividad pero nunca la agregaba al mapa con Map.addLayer, y el curso nunca daba el código para convertir ese mapa continuo en polígonos de target. El bloque de abajo corrige ambas cosas, y además usa .unmask(0) para que, si tu ROI no tiene cobertura ASTER SWIR, esa capa faltante sume 0 en vez de anular todo el resultado.
GEE JavaScript — FusiónSuma ponderada + visualización
// safeNorm(): normaliza [0,1] y además rellena con 0 los píxeles sin datos
// (por ejemplo, si tu ROI no tiene cobertura ASTER SWIR) para que un solo
// sensor faltante no anule todo el mapa de prospectividad.
var safeNorm = function(img) {
  return normalize01(img).unmask(0);
};

var prospectividad = safeNorm(alunitaIndex.max(caolinitaIndex).max(sericitaIndex)).multiply(0.30)
  .add(safeNorm(arcillaS2).multiply(0.25))
  .add(safeNorm(feOxideS2).multiply(0.20))
  .add(safeNorm(siliceTirIndex).multiply(0.15))
  .add(safeNorm(feOxideL8).multiply(0.10))
  .rename('prospectividad');

Map.addLayer(prospectividad,
  {min: 0, max: 1, palette: ['1a1a2e', '3b0f70', '8c2981', 'de4968', 'fe9f6d', 'fcfdbf']},
  'Prospectividad integrada');
Paso 3 — Vectorizar y clasificar targets

El mapa de prospectividad es continuo (cada píxel vale entre 0 y 1). Para obtener polígonos concretos que puedas llevar a campo, hay que aplicar un umbral, agrupar los píxeles contiguos que lo superan, y calificar cada polígono resultante según su valor medio.

🥇
Prioridad 1 — Crítica
Prospectividad media > 0.65 en un polígono > 0.5 Ha. Señal mineral fuerte y persistente entre sensores. Acción: calicatas inmediatas + muestreo geoquímico sistemático.
🥈
Prioridad 2 — Alta
Prospectividad entre 0.45 y 0.65, o señal fuerte en un solo sensor sin confirmación cruzada. Acción: mapeo geológico de detalle + revisión con otra fecha/sensor.
🥉
Prioridad 3 — Moderada
Resto de anomalías con área mínima suficiente. Señal débil o alta cobertura vegetal. Acción: muestreo geoquímico de reconocimiento.
GEE JavaScript — TargetsUmbral, vectorización y prioridad
var UMBRAL_TARGET  = 0.35; // umbral mínimo para considerar "anomalía" — súbelo/bájalo según tu histograma
var AREA_MINIMA_HA = 0.5;  // descarta polígonos de 1 píxel (ruido)

var targetsRaw = prospectividad.gte(UMBRAL_TARGET).selfMask();

var targets = targetsRaw.reduceToVectors({
  reducer: ee.Reducer.countEvery(),
  geometry: roi,
  scale: 30,
  geometryType: 'polygon',
  eightConnected: true,
  labelProperty: 'id_target',
  bestEffort: true
});

targets = targets.map(function (f) {
  var areaHa = f.geometry().area().divide(10000);
  var stats = prospectividad.reduceRegion({
    reducer: ee.Reducer.mean(), geometry: f.geometry(), scale: 30, bestEffort: true
  });
  var media = ee.Number(stats.get('prospectividad'));
  var prioridad = ee.Algorithms.If(media.gte(0.65), 'P1 - Critica',
                    ee.Algorithms.If(media.gte(0.45), 'P2 - Alta', 'P3 - Moderada'));
  return f.set({area_ha: areaHa, prospectividad_media: media, prioridad: prioridad});
}).filter(ee.Filter.gt('area_ha', AREA_MINIMA_HA));

print('Targets detectados (tras filtrar por área mínima):', targets.size());
print('Tabla de targets:', targets);

var p1 = targets.filter(ee.Filter.eq('prioridad', 'P1 - Critica'));
var p2 = targets.filter(ee.Filter.eq('prioridad', 'P2 - Alta'));
var p3 = targets.filter(ee.Filter.eq('prioridad', 'P3 - Moderada'));

Map.addLayer(p3.style({color: '388bfd', fillColor: '00000000', width: 2}), {}, 'Targets · Prioridad 3');
Map.addLayer(p2.style({color: 'e3b341', fillColor: '00000000', width: 2}), {}, 'Targets · Prioridad 2');
Map.addLayer(p1.style({color: 'f85149', fillColor: '00000000', width: 3}), {}, 'Targets · Prioridad 1');
Paso 4 — Estadísticas y panel interactivo de inspección

Este bloque agrega un panel en la esquina del mapa: al hacer clic en cualquier punto, te muestra al instante el valor de la prospectividad y de cada índice individual en ese lugar. También imprime un histograma con la distribución de prospectividad de todo tu ROI.

GEE JavaScript — InspecciónPanel de clic + histograma
// Panel + histograma de estadísticas
var panel = ui.Panel({
  style: {position: 'bottom-left', padding: '10px', width: '280px', backgroundColor: 'rgba(20,20,20,0.85)'}
});
panel.add(ui.Label('👆 Haz clic en cualquier punto del mapa', {color: 'white', fontWeight: 'bold'}));
Map.add(panel);

var capasParaInspeccionar = ee.Image.cat([
  prospectividad,
  alunitaIndex, caolinitaIndex, sericitaIndex,
  arcillaS2, feOxideS2,
  siliceTirIndex, feOxideL8
]);

Map.onClick(function (coords) {
  var punto = ee.Geometry.Point([coords.lon, coords.lat]);
  var valores = capasParaInspeccionar.reduceRegion({
    reducer: ee.Reducer.first(), geometry: punto, scale: 30
  });

  valores.evaluate(function (resultado) {
    panel.clear();
    panel.add(ui.Label('📍 ' + coords.lon.toFixed(4) + ', ' + coords.lat.toFixed(4),
      {color: 'white', fontWeight: 'bold'}));
    for (var nombre in resultado) {
      var valor = resultado[nombre];
      var texto = (valor === null) ? 'sin datos' : valor.toFixed(3);
      panel.add(ui.Label(nombre + ':  ' + texto, {color: '#dddddd', fontSize: '12px'}));
    }
  });
});

print(ui.Chart.image.histogram({image: prospectividad, region: roi, scale: 30, maxBuckets: 40})
  .setOptions({
    title: 'Distribución de prospectividad en el ROI',
    hAxis: {title: 'Prospectividad (0-1)'},
    vAxis: {title: 'N° de píxeles'}
  }));

El clic no funciona sobre el visor estático de esta página — solo dentro del Code Editor de GEE, donde Map es un mapa interactivo real.

Paso 5 — Exportar productos listos para GIS
1
GeoJSON — Targets con atributos
Polígonos vectorizados a partir del umbral de prioridad, con campos prioridad, prospectividad_media y área en Ha. Importable directo en QGIS/ArcGIS.
GeoJSON · Vectorial
2
Stack raster multisensor
Todos los índices normalizados (Sentinel-2, ASTER y Landsat) más la prospectividad integrada, en un único GeoTIFF multibanda.
GeoTIFF · 30 m · Float32
3
RGB mineralógico — falso color
R = Alunita/Caolinita, G = Arcilla_OH (S2), B = Óxidos de Fe. Escalado a 8 bits para visualización directa en cualquier SIG.
GeoTIFF · Byte (0–255)
4
CSV — Estadísticas zonales por target
Valor medio de cada índice por polígono-target, para priorizar la ruta de campo antes de salir.
CSV · Tabular
GEE JavaScript — Exportación4 tareas reales a Google Drive
// 1. Targets como GeoJSON (para QGIS/ArcGIS)
Export.table.toDrive({
  collection: targets,
  description: 'Targets_GeoJSON',
  folder: 'exploracion_mineral',
  fileNamePrefix: 'targets',
  fileFormat: 'GeoJSON'
});

// 2. Estadísticas de los targets como CSV
Export.table.toDrive({
  collection: targets,
  description: 'Targets_CSV',
  folder: 'exploracion_mineral',
  fileNamePrefix: 'targets_estadisticas',
  fileFormat: 'CSV'
});

// 3. Stack multisensor completo (todos los índices + prospectividad)
Export.image.toDrive({
  image: ee.Image.cat([
    prospectividad,
    alunitaIndex, caolinitaIndex, sericitaIndex, oxidosVnirIndex,
    siliceTirIndex, arcillaS2, feOxideS2, ferrosoS2, feOxideL8, arcillaL8
  ]),
  description: 'Stack_Multisensor',
  folder: 'exploracion_mineral',
  fileNamePrefix: 'stack_multisensor',
  region: roi,
  scale: 30,
  crs: 'EPSG:4326',
  maxPixels: 1e9
});

// 4. RGB mineralógico en 8 bits, listo para abrir en cualquier SIG
var rgbMineral = ee.Image.cat([
  alunitaIndex.max(caolinitaIndex).max(sericitaIndex),
  arcillaS2,
  feOxideS2
]).unitScale(0.8, 1.6).multiply(255).byte();

Export.image.toDrive({
  image: rgbMineral,
  description: 'RGB_Mineralogico',
  folder: 'exploracion_mineral',
  fileNamePrefix: 'rgb_mineralogico',
  region: roi,
  scale: 10,
  crs: 'EPSG:4326',
  maxPixels: 1e9
});

print('4 tareas de exportación creadas: abre la pestaña "Tasks" (arriba a la derecha) y pulsa RUN en cada una.');
📂
Las exportaciones no se disparan solas
Export.*.toDrive() solo crea la tarea. Tienes que ir a la pestaña Tasks (arriba a la derecha del Code Editor) y pulsar RUN en cada una para que realmente se generen los archivos en tu Google Drive, dentro de la carpeta exploracion_mineral.
Workflow recomendado — de GEE al campo
📍
Paso 1
Definir tu ROI en PARAMS (Módulo 2)
→
▶
Paso 2
Ejecutar los 3 bloques de sensores
→
🧮
Paso 3
Fusionar y ajustar pesos si falta ASTER
→
⚙
Paso 4
Lanzar exportaciones desde Tasks
→
🗺
Paso 5
QGIS + geología regional
→
⛏
Paso 6
Campaña de campo en targets P1
🔬
Advertencia científica fundamental
Los tres sensores detectan halos de alteración hidrotermal — proxies indirectos de mineralización. La presencia de alunita, óxidos de Fe o arcillas en teledetección no confirma un depósito económico: existen sistemas alterados sin mineralización rentable. La confirmación requiere muestreo geoquímico y, eventualmente, perforación exploratoria. Este sistema prioriza el trabajo de campo — no sustituye la verificación en terreno.
Bibliografía

Todas las fórmulas y afirmaciones técnicas de este curso están respaldadas por estas fuentes. Se verificaron una a una contra las publicaciones originales; la referencia "Agar & Coulter 2007" del material previo no correspondía a ninguna publicación localizable y fue sustituida por Rowan et al. (2003), la fuente correcta y verificable para ese índice.

Referencias científicas
1.Sabins, F. F. (1999). Remote sensing for mineral exploration. Ore Geology Reviews, 14(3–4), 157–183. doi.org/10.1016/S0169-1368(99)00007-4
2.Ninomiya, Y. (2002). Mapping quartz, carbonate minerals and mafic–ultramafic rocks using remotely sensed multispectral thermal infrared ASTER data. Proceedings of SPIE, 4710 (Thermosense XXIV), 191–202.
3.Ninomiya, Y. (2003). A stabilized vegetation index and several mineralogic indices defined for ASTER VNIR and SWIR data. Proceedings of IGARSS 2003, IEEE International Geoscience and Remote Sensing Symposium.Define los índices de alunita/caolinita/OH que se usan en el Módulo 4 (fórmulas simplificadas con fines didácticos).
4.Rowan, L. C. & Mars, J. C. (2003). Lithologic mapping in the Mountain Pass, California area using Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER) data. Remote Sensing of Environment, 84(3), 350–366. doi.org/10.1016/S0034-4257(02)00127-X
5.Rowan, L. C., Hook, S. J., Abrams, M. J. & Mars, J. C. (2003). Mapping hydrothermally altered rocks at Cuprite, Nevada, using the Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER), a new satellite-imaging system. Economic Geology, 98(5), 1019–1027.Reemplaza a "Agar & Coulter 2007" como fuente del índice de alunita en el Módulo 4 — es el estudio de validación clásico de índices SWIR de ASTER en un sitio de alteración muy bien caracterizado.
6.Corbett, G. J. & Leach, T. M. (1998). Southwest Pacific Rim Gold–Copper Systems: Structure, Alteration, and Mineralization. Society of Economic Geologists, Special Publication No. 6, 236 p.
7.Crósta, A. P. & Moore, J. M. (1989). Enhancement of Landsat Thematic Mapper imagery for residual soil mapping in SW Minas Gerais State, Brazil: a prospecting case history in greenstone belt terrain. Proceedings of the 7th ERIM Thematic Conference: Remote Sensing for Geology, 1173–1187.Origen de la "técnica Crósta" de PCA sobre bandas de óxidos de Fe, mencionada en el Módulo 5.
8.van der Werff, H. & van der Meer, F. (2016). Sentinel-2A MSI and Landsat 8 OLI provide data continuity for geological remote sensing. Remote Sensing, 8(11), 883. doi.org/10.3390/rs8110883Referencia añadida en esta actualización — respalda directamente la estrategia multisensor de los Módulos 3 y 5.
Documentación técnica oficial
·European Space Agency (2015). Sentinel-2 User Handbook. Copernicus Programme. sentinel.esa.int
·USGS Earth Resources Observation and Science (EROS) Center (2020). Landsat 8-9 OLI/TIRS Collection 2 Level-2 Science Products [dataset]. U.S. Geological Survey. doi.org/10.5066/P9OGBGM6Fuente de los factores de escala (0.0000275 / −0.2) usados en el Módulo 5.
·NASA LP DAAC / METI (2001). ASTER L1T Product User Guide. Land Processes Distributed Active Archive Center. lpdaac.usgs.gov
Módulo 6 de 6
Anexo — Estudio de caso real

Sistema mono-sensor: ASTER + DEM + Machine Learning

Los Módulos 1–6 enseñan el enfoque multisensor con ROI configurable. Este anexo documenta un caso real distinto: un sistema que profundiza en un solo sensor (ASTER) combinado con relieve (DEM) y clustering no supervisado (K-Means), aplicado a un polígono minero fijo. Sirve como segunda referencia — más profundidad mineralógica y estructural, a cambio de depender 100% de que ASTER cubra tu zona.

¿Cuándo usar este enfoque en vez del multisensor?
Usa el enfoque multisensor (Módulos 1–6)
Cuando no sabes si ASTER cubre tu zona, cuando trabajas varias zonas distintas, o cuando priorizas velocidad y cobertura garantizada (Sentinel-2/Landsat) sobre la máxima resolución mineralógica.
Usa este enfoque mono-sensor + ML
Cuando ya confirmaste que ASTER SWIR cubre bien tu concesión, tienes un DEM propio de buena resolución, y quieres exprimir la máxima discriminación mineralógica más un clustering no supervisado sobre toda la escena.
Arquitectura del sistema
⛰
DEM propio
Pendiente, aspecto y sombreado desde un DEM subido como Asset. Define estructuralFav: pendientes 15°–40°, la ventana morfológica más favorable para trampas estructurales.
🛰
ASTER — 8 índices SWIR/TIR/VNIR
Alunita, caolinita, sericita, óxidos de Fe, OH, propilítica, sílice TIR y carbonatos TIR — la misma familia de índices del Módulo 4, aplicada aquí con más detalle sobre una sola escena.
🧠
K-Means no supervisado
Agrupa los píxeles en 8 clases sin etiquetas previas, a partir de bandas ASTER + índices + pendiente. Requiere inspección visual humana para decidir qué clusters son prospectivos.
🐛
Registro de depuración — 5 errores reales encontrados y corregidos
Esta versión (v2.0 del usuario) llegó con un síntoma concreto: "existe imagen ASTER pero no aparece". La causa no era un solo bug sino una combinación de cinco problemas, típica de scripts de GEE que crecen por iteraciones sucesivas.
#Síntoma / riesgoCausa realCorrección aplicada
1 "No aparece nada de ASTER" Sin filterDate + CLOUDCOVER<10 muy estricto → fácil terminar con 0 escenas, sin ningún aviso. Diagnóstico en cascada: cuenta escenas, reintenta sin nubes, reintenta sin fecha, e informa la causa exacta con print().
2 Ninguna imagen ASTER cruda visible Solo se visualizaban productos derivados (índices), casi todos con shown=false. Se agregó una capa 📷 ASTER RGB visible por defecto, para confirmar cobertura a simple vista.
3 La capa "Brechas Conocidas" desaparecía al hacer clic Map.layers().set(12, ...) con índice fijo — sobrescribía silenciosamente la capa que ocupaba esa posición. Capa dedicada creada una sola vez; el clic actualiza su contenido con markerLayer.setEeObject(point).
4 Clusters K-Means poco informativos Un único unitScale(0,10000) para bandas de escalas muy distintas (DN 1–255, índices ~0.8–2.5, pendiente 0–90°) aplastaba casi todo a ~0. Cada banda se normaliza individualmente con percentiles 5–95 (normalize01), igual que en el Módulo 6.
5 Filtro de nubes descartaba casi todo CLOUDCOVER<10 es muy exigente para zonas con nubosidad frecuente. Subido a 30% como punto de partida, con reintento automático si aún da 0.
Los 8 índices mineralógicos usados (idénticos al Módulo 4)
ÍndiceFórmulaPeso en favorabilidad Au
Alunita
Rowan et al. 2003 — Cuprite
B7 / B530%
Sílice TIR
Ninomiya 2003
(B13/B12)+(B13/B14)25%
Caolinita
Ninomiya 2002
B6 / B515%
Óxidos de Fe
Sabins 1999
B2 / B115%
Sericita
Ninomiya 2002
B5 / B68%
Grupos OH
Rowan & Mars 2003
(B6+B9)/(B8×2)7%
Propilítica/Clorita
Corbett & Leach 1998
B4 / B5—
Carbonatos TIR
Ninomiya 2003
(B13+B11)/(B12×2)—
Código completo corregido (v2.1) — listo para pegar en GEE

Este es un script monolítico (no está pensado para pegarse por fragmentos como en los Módulos 1–6). Cópialo completo de una sola vez en el Code Editor de GEE.

GEE JavaScript — Sistema completov2.1 corregido · 672 líneas
/*
================================================================================
SISTEMA DE EXPLORACIÓN MINERAL AUTÓNOMO - v2.1 Autor Spatialmind S.R.L.
Detección de Alteración Hidrotermal Asociada a Mineralización Aurífera
Sin Muestras de Campo - Basado 100% en Teledetección + ML No Supervisado

REFERENCIAS:
  - Sabins (1999) — Remote Sensing: Principles and Interpretation
  - Rowan et al. (2003) — Mapping hydrothermally altered rocks at Cuprite (Economic Geology)
  - Rowan & Mars (2003) — Lithologic mapping in the Mountain Pass area
  - Ninomiya (2003) — Silica index and carbonate index from ASTER TIR
  - Corbett & Leach (1998) — Southwest Pacific Rim gold-copper systems
================================================================================
*/

// ════════════════════════════════════════════════════════════════════════════
// 0. CONFIGURACIÓN INICIAL
// ════════════════════════════════════════════════════════════════════════════
var aoiLocal = ee.FeatureCollection('projects/eddycc66/assets/POLIGONO_TIGRES_DE_ORO').geometry();
var brechas  = ee.FeatureCollection('projects/eddycc66/assets/Brechas_dorado');

Map.centerObject(aoiLocal, 13);
Map.addLayer(aoiLocal, {color: 'white'}, '🔲 Límite del AOI', true);

// ════════════════════════════════════════════════════════════════════════════
// 1. PROCESAMIENTO TOPOGRÁFICO
// ════════════════════════════════════════════════════════════════════════════
var dem       = ee.Image('projects/eddycc66/assets/dem_tigres_de_oro').clip(aoiLocal);
var slope     = ee.Terrain.slope(dem);
var aspect    = ee.Terrain.aspect(dem);
var hillshade = ee.Terrain.hillshade(dem);

// Zonas estructuralmente favorables: pendientes moderadas (15°–40°)
var estructuralFav = slope.gt(15).and(slope.lt(40)).rename('Estructural');

// ════════════════════════════════════════════════════════════════════════════
// 2. PROCESAMIENTO ASTER MULTI-ESPECTRAL — CON DIAGNÓSTICO Y REINTENTOS
// ════════════════════════════════════════════════════════════════════════════
var FECHA_INICIO = '2000-01-01';
var FECHA_FIN    = '2008-04-28'; // límite real del SWIR (falló en abril 2008)
var MAX_NUBES    = 30;           // 10% era muy estricto; súbelo más si sigue en 0

var asterColl = ee.ImageCollection('ASTER/AST_L1T_003')
  .filterBounds(aoiLocal)
  .filterDate(FECHA_INICIO, FECHA_FIN)
  .filter(ee.Filter.lt('CLOUDCOVER', MAX_NUBES));

var nEscenas = asterColl.size().getInfo();
print('① Escenas ASTER SWIR válidas (fecha ' + FECHA_INICIO + ' a ' + FECHA_FIN +
      ', nubes <' + MAX_NUBES + '%):', nEscenas);

if (nEscenas === 0) {
  print('⚠ 0 escenas con ese filtro de nubes. Reintentando sin filtro de nubes...');
  asterColl = ee.ImageCollection('ASTER/AST_L1T_003')
    .filterBounds(aoiLocal)
    .filterDate(FECHA_INICIO, FECHA_FIN);
  nEscenas = asterColl.size().getInfo();
  print('② Escenas ASTER SWIR sin filtro de nubes:', nEscenas);
}

if (nEscenas === 0) {
  print('⚠ Sigue en 0 dentro de la ventana SWIR (2000–2008). Ampliando a TODO ' +
        'el archivo ASTER (VNIR/TIR seguirán funcionando; el SWIR puede no ser confiable)...');
  asterColl = ee.ImageCollection('ASTER/AST_L1T_003').filterBounds(aoiLocal);
  nEscenas = asterColl.size().getInfo();
  print('③ Escenas ASTER de cualquier fecha sobre el AOI:', nEscenas);
}

if (nEscenas === 0) {
  print('🛑 CAUSA CONFIRMADA: no existe ninguna escena ASTER (de ningún tipo) sobre ' +
        'tu polígono POLIGONO_TIGRES_DE_ORO en el catálogo de Google Earth Engine. ' +
        'Por eso no aparece ninguna imagen ni índice derivado de ASTER — no es un ' +
        'error de código, es que no hay dato fuente. Revisa que las coordenadas de ' +
        'ese asset sean correctas, o usa Sentinel-2/Landsat en su lugar para esta zona.');
} else {
  print('✅ Hay cobertura ASTER utilizable — si aun así no ves la capa "📷 ASTER RGB" ' +
        'en el mapa, revisa que su casilla esté marcada en el panel Layers (arriba a la derecha del mapa).');
}

var asterMedian = asterColl.median().clip(aoiLocal);

// Bandas VNIR (15 m) + SWIR (30 m)
// B01=0.52–0.60μm  B02=0.63–0.69μm  B3N=0.78–0.86μm
// B04=1.60–1.70μm  B05=2.145–2.185μm B06=2.185–2.225μm
// B07=2.235–2.285μm B08=2.295–2.365μm B09=2.360–2.430μm
var asterBands = ['B01','B02','B3N','B04','B05','B06','B07','B08','B09'];
var asterNames = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'];
var vnirSwir   = asterMedian.select(asterBands, asterNames);

// Bandas TIR (90 m)
// B10=8.125–8.475μm  B11=8.475–8.825μm  B12=8.925–9.275μm
// B13=10.25–10.95μm  B14=10.95–11.65μm
var tirBands = asterMedian.select(['B10','B11','B12','B13','B14']);

// ── Capa cruda visible: esto es lo que faltaba en la v2.0 ────────────────
// Falso color estándar ASTER: R=NIR(B3) G=Rojo(B2) B=Verde(B1)
var asterRGB = vnirSwir.select(['B3', 'B2', 'B1']);
Map.addLayer(asterRGB, {min: 20, max: 130}, '📷 ASTER RGB (falso color, verifica cobertura)', true);

// ════════════════════════════════════════════════════════════════════════════
// 3. ÍNDICES MINERALÓGICOS
// ════════════════════════════════════════════════════════════════════════════

// ─── 3.1 SÍLICE (TIR) ─────────────────────────────────────────────────────
// Ninomiya (2003): Silica Index compuesto — más robusto que ratio simple
var siliceIndex = tirBands.select('B13').divide(tirBands.select('B12').add(0.001))
  .add(tirBands.select('B13').divide(tirBands.select('B14').add(0.001)))
  .rename('Silice_Index');

// ─── 3.2 ALUNITA (SWIR) ───────────────────────────────────────────────────
// Rowan et al. (2003, Cuprite): absorción ~2.17μm (B5), reflectancia ~2.26μm (B7)
var alunitaIndex = vnirSwir.select('B7')
  .divide(vnirSwir.select('B5').add(0.001))
  .rename('Alunita_Index');

// ─── 3.3 CAOLINITA (SWIR) ─────────────────────────────────────────────────
var caolintaIndex = vnirSwir.select('B6')
  .divide(vnirSwir.select('B5').add(0.001))
  .rename('Caolinita_Index');

// ─── 3.4 SERICITA / MUSCOVITA (SWIR) ─────────────────────────────────────
// Ninomiya (2002): absorción principal sericita en ~2.20μm (B5)
var sericitaIndex = vnirSwir.select('B5')
  .divide(vnirSwir.select('B6').add(0.001))
  .rename('Sericita_Index');

// ─── 3.5 ÓXIDOS DE HIERRO (VNIR) ──────────────────────────────────────────
var oxidosIndex = vnirSwir.select('B2')
  .divide(vnirSwir.select('B1').add(0.001))
  .rename('Oxidos_Index');

// ─── 3.6 GRUPOS OH — ARCILLAS + CARBONATOS (SWIR) ─────────────────────────
// Rowan & Mars (2003): (B6+B9)/(B8×2)
var ohIndex = vnirSwir.select('B6')
  .add(vnirSwir.select('B9'))
  .divide(vnirSwir.select('B8').multiply(2).add(0.001))
  .rename('OH_Index');

// ─── 3.7 PROPILÍTICA / CLORITA (SWIR) ────────────────────────────────────
var propiliticaIndex = vnirSwir.select('B4')
  .divide(vnirSwir.select('B5').add(0.001))
  .rename('Propilitica_Index');

// ─── 3.8 CARBONATOS (TIR) ─────────────────────────────────────────────────
var carbonatoIndex = tirBands.select('B13').add(tirBands.select('B11'))
  .divide(tirBands.select('B12').multiply(2).add(0.001))
  .rename('Carbonato_Index');

// ─── 3.9 NDVI (para máscara de vegetación) ────────────────────────────────
var ndvi = vnirSwir.normalizedDifference(['B3','B2']).rename('NDVI');

// ════════════════════════════════════════════════════════════════════════════
// 4. FUNCIÓN DE NORMALIZACIÓN ROBUSTA (percentiles 5–95)
// ════════════════════════════════════════════════════════════════════════════
var normalize01 = function(img) {
  var bandName = img.bandNames().get(0);
  var stats = img.reduceRegion({
    reducer: ee.Reducer.percentile([5, 95]),
    geometry: aoiLocal,
    scale: 90,
    bestEffort: true,
    maxPixels: 1e9
  });
  var vals   = stats.values();
  var minVal = ee.Number(vals.get(0));
  var maxVal = ee.Number(vals.get(1));
  var diff   = maxVal.subtract(minVal);
  var safeMax = ee.Algorithms.If(diff.gt(1e-10), maxVal, minVal.add(1));
  return img
    .subtract(minVal)
    .divide(ee.Number(safeMax).subtract(minVal))
    .clamp(0, 1)
    .rename([bandName]);
};

// ════════════════════════════════════════════════════════════════════════════
// 5. MODELO DE FAVORABILIDAD PARA SISTEMA EPITERMAL AU
// ════════════════════════════════════════════════════════════════════════════
//   Alunita      30% — alteración argílica avanzada, proxy más diagnóstico de Au
//   Sílice       25% — silicificación hidrotermal
//   Caolinita    15% — asociada a AAS y Au supérgeno
//   Óxidos Fe    15% — gossans / halos de oxidación
//   Sericita      8% — alteración fílica
//   OH general    7% — halo externo de alteración

var favorabilidadAu = normalize01(alunitaIndex).multiply(0.30)
  .add(normalize01(siliceIndex).multiply(0.25))
  .add(normalize01(caolintaIndex).multiply(0.15))
  .add(normalize01(oxidosIndex).multiply(0.15))
  .add(normalize01(sericitaIndex).multiply(0.08))
  .add(normalize01(ohIndex).multiply(0.07))
  .rename('Favorabilidad_Au');

// ════════════════════════════════════════════════════════════════════════════
// 6. STACK DE VARIABLES PARA CLUSTERING ML — NORMALIZACIÓN CORREGIDA
// ════════════════════════════════════════════════════════════════════════════
var mlInput = ee.Image.cat([
  vnirSwir.select(['B1','B2','B3','B4','B5','B6','B7','B8','B9']),
  siliceIndex,
  alunitaIndex,
  caolintaIndex,
  sericitaIndex,
  oxidosIndex,
  ohIndex,
  propiliticaIndex,
  slope,
  estructuralFav
]).unmask(0);

// CORREGIDO: antes un único unitScale(0,10000) aplastaba casi todas las
// bandas a ~0 (DN 1-255, índices ~0.8-2.5 y pendiente 0-90° no comparten
// escala). Ahora cada banda se normaliza individualmente con percentiles.
var mlBandNames    = mlInput.bandNames();
var mlNormalizadas = mlBandNames.map(function (name) {
  return normalize01(mlInput.select([name]));
});
var mlInputNorm = ee.ImageCollection.fromImages(mlNormalizadas).toBands().rename(mlBandNames);

// ════════════════════════════════════════════════════════════════════════════
// 7. CLUSTERING NO SUPERVISADO — K-MEANS
// ════════════════════════════════════════════════════════════════════════════
var samplesForClustering = mlInputNorm.sample({
  region: aoiLocal,
  scale: 60,
  numPixels: 3000,
  seed: 42,
  geometries: false
});

var clusterer = ee.Clusterer.wekaKMeans({
  nClusters: 8,
  init: 0,
  canopies: false,
  maxIterations: 100,
  distanceFunction: 'Euclidean'
}).train(samplesForClustering);

var clusters = mlInputNorm.cluster(clusterer).rename('Cluster');

// ════════════════════════════════════════════════════════════════════════════
// 8. ZONA DE MÁXIMA PRIORIDAD MINERAL
//    INSTRUCCIONES: inspecciona visualmente los clusters, anota cuáles
//    coinciden con zonas de alta favorabilidad y actualiza prospectiveClusters
// ════════════════════════════════════════════════════════════════════════════
var prospectiveClusters = ee.List([2, 5, 6]); // ← AJUSTAR TRAS INSPECCIÓN VISUAL

var targetClusters = prospectiveClusters.iterate(function (clusterID, image) {
  var mask = clusters.eq(ee.Number(clusterID));
  return ee.Image(image).or(mask);
}, ee.Image(0));
targetClusters = ee.Image(targetClusters).selfMask().rename('Target_Clusters');

// Máscaras de calidad
var mascaraVegetacion = ndvi.lt(0.3);          // excluir vegetación densa
var mascaraPendiente  = slope.gt(8);           // excluir planicies

// Zona final: umbral de favorabilidad + cluster ML + máscaras
var zonaProspectiva = favorabilidadAu.gt(0.55)
  .and(targetClusters.eq(1))
  .and(mascaraVegetacion)
  .and(mascaraPendiente)
  .selfMask()
  .rename('Zona_Maxima_Prioridad');

// ════════════════════════════════════════════════════════════════════════════
// 9. VISUALIZACIÓN
// ════════════════════════════════════════════════════════════════════════════
Map.addLayer(hillshade, {min: 120, max: 250}, 'Relieve (Hillshade)', true, 0.35);

Map.addLayer(favorabilidadAu,
  {min: 0, max: 1, palette: ['#0000FF', '#00FFFF', '#00FF00', '#FFFF00', '#FF8C00', '#FF0000']},
  '1️⃣ Favorabilidad Au (Índices corregidos)', true);

Map.addLayer(clusters.randomVisualizer(), {}, '2️⃣ Clusters K-Means (8 clases)', true);

Map.addLayer(zonaProspectiva, {palette: ['#FF00FF']}, '🎯 ZONA MÁXIMA PRIORIDAD Au', true);

// Índices individuales (apagados por defecto — activa la casilla para diagnóstico)
Map.addLayer(siliceIndex,
  {min: 1.0, max: 2.4, palette: ['navy', 'white', 'red']},
  'Índice Sílice TIR (Ninomiya 2003)', false);

Map.addLayer(alunitaIndex,
  {min: 0.9, max: 1.3, palette: ['blue', 'yellow', 'orange']},
  'Índice Alunita SWIR', false);

Map.addLayer(sericitaIndex,
  {min: 0.9, max: 1.2, palette: ['purple', 'white', 'green']},
  'Índice Sericita SWIR', false);

Map.addLayer(oxidosIndex,
  {min: 0.8, max: 1.4, palette: ['white', 'brown', 'darkred']},
  'Índice Óxidos Fe (Gossans)', false);

Map.addLayer(caolintaIndex,
  {min: 0.9, max: 1.2, palette: ['lightblue', 'cyan', 'blue']},
  'Índice Caolinita SWIR', false);

Map.addLayer(propiliticaIndex,
  {min: 0.8, max: 1.3, palette: ['darkgreen', 'lime', 'yellow']},
  'Índice Propilítica/Clorita (B4/B5)', false);

Map.addLayer(ohIndex,
  {min: 0.9, max: 1.15, palette: ['gray', 'pink', 'magenta']},
  'Índice OH — Arcillas+Carbonatos', false);

// RGB falso color mineralógico: R=Alunita, G=Sericita, B=Óxidos
var rgbMineralogico = ee.Image.cat([
  alunitaIndex.unitScale(0.9, 1.3),
  sericitaIndex.unitScale(0.9, 1.2),
  oxidosIndex.unitScale(0.8, 1.4)
]);
Map.addLayer(rgbMineralogico, {min: 0, max: 1}, 'RGB Mineralógico (Alunita/Sericita/Óxidos)', false);

Map.addLayer(brechas, {color: '00FF00'}, '✅ Brechas Conocidas (Validación)', true, 0.7);

// ════════════════════════════════════════════════════════════════════════════
// 10. UI — INSPECTOR DE CLUSTER EN TIEMPO REAL
// ════════════════════════════════════════════════════════════════════════════
var clusterPanel = ui.Panel({
  style: {position: 'top-right', padding: '10px', width: '220px',
          border: '1px solid #ccc', backgroundColor: 'white'}
});
clusterPanel.add(ui.Label('🔍 INSPECTOR DE CLUSTER', {fontWeight: 'bold', color: 'navy'}));
clusterPanel.add(ui.Label('Haz clic en el mapa', {fontSize: '12px', color: 'gray'}));
var clusterLabel    = ui.Label('Cluster: —');
var favLabel        = ui.Label('Favorabilidad: —');
var interpretaLabel = ui.Label('Tipo: —', {fontSize: '12px', color: 'gray'});
clusterPanel.add(clusterLabel);
clusterPanel.add(favLabel);
clusterPanel.add(interpretaLabel);
Map.add(clusterPanel);

// ════════════════════════════════════════════════════════════════════════════
// 11. UI — PANEL ESPECTRAL INTERACTIVO
// ════════════════════════════════════════════════════════════════════════════
var inspector = ui.Panel({
  style: {width: '440px', position: 'bottom-left', padding: '10px',
          border: '2px solid navy', backgroundColor: 'white'}
});
ui.root.insert(0, inspector);

inspector.add(ui.Label('📊 ANÁLISIS ESPECTRAL Y MINERALÓGICO',
  {fontWeight: 'bold', fontSize: '15px', color: 'navy'}));
inspector.add(ui.Label('Haz clic en el mapa para analizar un píxel',
  {fontSize: '12px', color: 'gray', margin: '0 0 8px 0'}));

// CORREGIDO: capa dedicada para el marcador de clic, en vez de
// Map.layers().set(12, ...) que sobrescribía "Brechas Conocidas".
var markerLayer = ui.Map.Layer(ee.Geometry.MultiPoint([]), {color: 'yellow'}, '📍 Punto analizado', false);
Map.layers().add(markerLayer);

// ════════════════════════════════════════════════════════════════════════════
// 12. EVENTO OnClick — INSPECCIÓN DE PÍXEL
// ════════════════════════════════════════════════════════════════════════════
Map.onClick(function (coords) {
  var point = ee.Geometry.Point([coords.lon, coords.lat]);

  // Actualizar inspector de cluster (panel derecho)
  var clusterVal = clusters.reduceRegion({
    reducer: ee.Reducer.first(), geometry: point, scale: 30
  });
  clusterVal.evaluate(function (r) {
    if (r && r.Cluster !== null && r.Cluster !== undefined) {
      clusterLabel.setValue('Cluster: ' + Math.round(r.Cluster));
    }
  });

  inspector.clear();
  inspector.add(ui.Label('⏳ Procesando...', {color: 'blue', fontWeight: 'bold'}));

  markerLayer.setEeObject(point);
  markerLayer.setShown(true);

  // Firma espectral VNIR-SWIR
  var chart = ui.Chart.image.regions({
    image: vnirSwir,
    regions: point,
    reducer: ee.Reducer.mean(),
    scale: 30,
    xLabels: [0.56, 0.66, 0.81, 1.65, 2.165, 2.205, 2.26, 2.33, 2.395]
  }).setOptions({
    title: 'Firma Espectral VNIR-SWIR (ASTER)',
    hAxis: {title: 'Longitud de Onda (μm)', titleTextStyle: {italic: false}},
    vAxis: {title: 'Reflectancia (DN)', titleTextStyle: {italic: false}},
    lineWidth: 3, pointSize: 6,
    series: {0: {color: 'darkred'}},
    legend: 'none',
    backgroundColor: '#f5f5f5'
  });

  // Valores de todos los índices
  var valores = ee.Image.cat([
    favorabilidadAu,
    siliceIndex,
    alunitaIndex,
    caolintaIndex,
    sericitaIndex,
    oxidosIndex,
    ohIndex,
    propiliticaIndex,
    clusters,
    ndvi
  ]).reduceRegion({
    reducer: ee.Reducer.first(), geometry: point, scale: 30
  });

  valores.evaluate(function (v) {
    inspector.clear();
    inspector.add(ui.Label('📊 ANÁLISIS ESPECTRAL Y MINERALÓGICO',
      {fontWeight: 'bold', fontSize: '15px', color: 'navy', margin: '0 0 8px 0'}));
    inspector.add(chart);
    inspector.add(ui.Label('─────────────────────────────────', {color: '#ccc'}));
    inspector.add(ui.Label('📈 ÍNDICES MINERALÓGICOS:',
      {fontWeight: 'bold', margin: '8px 0 4px 0'}));

    var fav = (v && v.Favorabilidad_Au !== null) ? v.Favorabilidad_Au : 0;
    var favColor = fav > 0.6 ? '#1a7a1a' : (fav > 0.4 ? '#cc7700' : '#cc0000');
    inspector.add(ui.Label('• Favorabilidad Au: ' + fav.toFixed(3),
      {fontWeight: 'bold', color: favColor}));

    var safe = function (val) { return (v && v[val] !== null && v[val] !== undefined) ? v[val].toFixed(3) : 'N/D'; };
    inspector.add(ui.Label('• Sílice TIR (Ninomiya): ' + safe('Silice_Index')));
    inspector.add(ui.Label('• Alunita SWIR:          ' + safe('Alunita_Index')));
    inspector.add(ui.Label('• Caolinita SWIR:        ' + safe('Caolinita_Index')));
    inspector.add(ui.Label('• Sericita B5/B6:        ' + safe('Sericita_Index')));
    inspector.add(ui.Label('• Óxidos Fe:             ' + safe('Oxidos_Index')));
    inspector.add(ui.Label('• OH (Rowan & Mars):     ' + safe('OH_Index')));
    inspector.add(ui.Label('• Propilítica B4/B5:     ' + safe('Propilitica_Index')));
    inspector.add(ui.Label('• NDVI:                  ' + safe('NDVI')));
    inspector.add(ui.Label('• Cluster ML:            ' +
      (v && v.Cluster !== null ? Math.round(v.Cluster) : 'N/D')));

    inspector.add(ui.Label('─────────────────────────────────', {color: '#ccc'}));
    inspector.add(ui.Label('🔬 INTERPRETACIÓN GEOLÓGICA:',
      {fontWeight: 'bold', margin: '8px 0 4px 0'}));

    var hayHallazgo = false;

    if (v && v.Alunita_Index > 1.05) {
      inspector.add(ui.Label('✓ Alteración argílica avanzada (AAS) — alta sulfuración Au',
        {color: '#1a7a1a', fontWeight: 'bold'}));
      hayHallazgo = true;
    }
    if (v && v.Silice_Index > 2.0) {
      inspector.add(ui.Label('✓ Silicificación intensa — posible brecha/stockwork/veta',
        {color: '#1a7a1a'}));
      hayHallazgo = true;
    }
    if (v && v.Oxidos_Index > 1.15) {
      inspector.add(ui.Label('✓ Gossan / halo de óxidos Fe — mineralización sulfurada oxidada',
        {color: '#cc7700'}));
      hayHallazgo = true;
    }
    if (v && v.Sericita_Index > 1.05) {
      inspector.add(ui.Label('✓ Alteración fílica (sericita) — epitermal baja sulfuración',
        {color: '#cc7700'}));
      hayHallazgo = true;
    }
    if (v && v.Caolinita_Index > 1.05 && v.Alunita_Index > 1.0) {
      inspector.add(ui.Label('✓ Caolinita + Alunita — zona de alteración supérgena sobre Au',
        {color: '#1a7a1a'}));
      hayHallazgo = true;
    }
    if (v && v.Propilitica_Index < 0.95) {
      inspector.add(ui.Label('• Posible propilítica/clorita — halo externo distal',
        {color: 'gray'}));
    }
    if (v && v.NDVI > 0.3) {
      inspector.add(ui.Label('⚠ NDVI alto — vegetación puede enmascarar señal espectral',
        {color: '#cc0000'}));
    }
    if (!hayHallazgo && fav < 0.4) {
      inspector.add(ui.Label('✗ Baja probabilidad de alteración hidrotermal',
        {color: '#cc0000'}));
    }

    favLabel.setValue('Favorabilidad: ' + fav.toFixed(3));
    var tipoSistema = '—';
    if (v && v.Alunita_Index > 1.05) tipoSistema = 'Alta sulfuración (Au)';
    else if (v && v.Sericita_Index > 1.05) tipoSistema = 'Baja sulfuración (Au-Ag)';
    else if (v && v.Oxidos_Index > 1.15) tipoSistema = 'Gossan / oxidado';
    interpretaLabel.setValue('Tipo: ' + tipoSistema);
  });
});

// ════════════════════════════════════════════════════════════════════════════
// 13. LEYENDA
// ════════════════════════════════════════════════════════════════════════════
var legend = ui.Panel({
  style: {position: 'bottom-right', padding: '8px 14px', backgroundColor: 'white',
          border: '1px solid #ccc'}
});
legend.add(ui.Label('EXPLORACIÓN Au — LEYENDA',
  {fontWeight: 'bold', fontSize: '13px', color: 'navy', margin: '0 0 6px 0'}));

var addRow = function (color, label) {
  var row = ui.Panel({layout: ui.Panel.Layout.Flow('horizontal'), style: {margin: '2px 0'}});
  row.add(ui.Label('█', {color: color, fontSize: '18px', margin: '0 6px 0 0'}));
  row.add(ui.Label(label, {fontSize: '12px'}));
  legend.add(row);
};

addRow('#FF00FF', 'Zona Máxima Prioridad Au');
addRow('#FF0000', 'Alta Favorabilidad (>0.75)');
addRow('#FF8C00', 'Moderada Favorabilidad (0.55–0.75)');
addRow('#FFFF00', 'Baja-Moderada (0.35–0.55)');
addRow('#00FF00', 'Brechas conocidas (validación)');
legend.add(ui.Label('────────────────', {color: '#ccc', margin: '4px 0'}));
legend.add(ui.Label('RGB Mineralógico:', {fontWeight: 'bold', fontSize: '12px'}));
legend.add(ui.Label('R=Alunita  G=Sericita  B=Óxidos', {fontSize: '11px', color: 'gray'}));
Map.add(legend);

// ════════════════════════════════════════════════════════════════════════════
// 14. EXPORTACIONES
//     NOTA: TIR se exporta a 90 m (resolución nativa), SWIR/VNIR a 30 m
// ════════════════════════════════════════════════════════════════════════════

// ── Producto 1: Stack completo VNIR+SWIR (30 m) ──────────────────────────
Export.image.toDrive({
  image: ee.Image.cat([
    zonaProspectiva.unmask(0).toFloat().rename('Zona_Prioridad_Au'),
    favorabilidadAu.toFloat().rename('Favorabilidad_Au'),
    clusters.toFloat().rename('Clusters_KMeans'),
    alunitaIndex.toFloat().rename('Alunita_B7B5'),
    caolintaIndex.toFloat().rename('Caolinita_B6B5'),
    sericitaIndex.toFloat().rename('Sericita_B5B6'),
    oxidosIndex.toFloat().rename('Oxidos_Fe_B2B1'),
    ohIndex.toFloat().rename('OH_B6B9_B8'),
    propiliticaIndex.toFloat().rename('Propilitica_B4B5')
  ]),
  description: 'Stack_Completo_VNIR_SWIR_30m',
  folder: 'GEE_Exploracion_Mineral_Au',
  region: aoiLocal,
  scale: 30,
  maxPixels: 1e9,
  crs: 'EPSG:4326'
});

// ── Producto 2: Índices TIR (90 m — resolución nativa) ────────────────────
Export.image.toDrive({
  image: ee.Image.cat([
    siliceIndex.toFloat().rename('Silice_Ninomiya2003'),
    carbonatoIndex.toFloat().rename('Carbonatos_TIR')
  ]),
  description: 'Indices_TIR_90m_Nativos',
  folder: 'GEE_Exploracion_Mineral_Au',
  region: aoiLocal,
  scale: 90,
  maxPixels: 1e9,
  crs: 'EPSG:4326'
});

// ── Producto 3: Mapa de favorabilidad Au (30 m) ───────────────────────────
Export.image.toDrive({
  image: favorabilidadAu.toFloat(),
  description: 'Favorabilidad_Au_Epitermal',
  folder: 'GEE_Exploracion_Mineral_Au',
  region: aoiLocal,
  scale: 30,
  maxPixels: 1e9,
  crs: 'EPSG:4326'
});

// ── Producto 4: Clusters K-Means (30 m) ──────────────────────────────────
Export.image.toDrive({
  image: clusters.toFloat(),
  description: 'Clusters_KMeans_8clases',
  folder: 'GEE_Exploracion_Mineral_Au',
  region: aoiLocal,
  scale: 30,
  maxPixels: 1e9,
  crs: 'EPSG:4326'
});

// ── Producto 5: RGB mineralógico para visualización (30 m) ────────────────
var rgbExport = ee.Image.cat([
  alunitaIndex.unitScale(0.9, 1.3).multiply(255),
  sericitaIndex.unitScale(0.9, 1.2).multiply(255),
  oxidosIndex.unitScale(0.8, 1.4).multiply(255)
]).toByte();

Export.image.toDrive({
  image: rgbExport,
  description: 'RGB_Mineralogico_Alunita_Sericita_Oxidos',
  folder: 'GEE_Exploracion_Mineral_Au',
  region: aoiLocal,
  scale: 30,
  maxPixels: 1e9,
  crs: 'EPSG:4326'
});

// ── Producto 6: Zona prospectiva vectorizada (shapefile) ──────────────────
var vectores = zonaProspectiva.reduceToVectors({
  geometry: aoiLocal,
  scale: 30,
  geometryType: 'polygon',
  eightConnected: false,
  maxPixels: 1e9,
  bestEffort: true
});

Export.table.toDrive({
  collection: vectores,
  description: 'Zonas_Prospectivas_Au_Vector',
  folder: 'GEE_Exploracion_Mineral_Au',
  fileFormat: 'SHP'
});

// ════════════════════════════════════════════════════════════════════════════
// 15. ESTADÍSTICAS Y RESUMEN CONSOLA
// ════════════════════════════════════════════════════════════════════════════
var pixelCount = zonaProspectiva.reduceRegion({
  reducer: ee.Reducer.count(),
  geometry: aoiLocal,
  scale: 30,
  maxPixels: 1e9
});

print('══════════════════════════════════════════════════════');
print('🥇 EXPLORACIÓN MINERAL Au — SISTEMA CORREGIDO v2.1');
print('══════════════════════════════════════════════════════');
print('📦 EXPORTACIONES (6 productos, en la pestaña Tasks):');
print('  1. Stack_Completo_VNIR_SWIR_30m.tif   (9 bandas)');
print('  2. Indices_TIR_90m_Nativos.tif         (2 bandas, 90 m)');
print('  3. Favorabilidad_Au_Epitermal.tif      (continua 0-1)');
print('  4. Clusters_KMeans_8clases.tif         (0-7)');
print('  5. RGB_Mineralogico_*.tif              (visualización)');
print('  6. Zonas_Prospectivas_Au_Vector.shp    (polígonos)');
print('══════════════════════════════════════════════════════');
print('📊 Píxeles en zona de máxima prioridad Au:');
print(pixelCount);
print('🎯 Clusters prospectivos (ajustar tras inspección):');
print(prospectiveClusters);
print('══════════════════════════════════════════════════════');
print('💡 WORKFLOW RECOMENDADO:');
print('  PASO 1 → Revisa el mensaje "①②③" de arriba: confirma que SÍ hay ASTER');
print('  PASO 2 → Activar capa "Clusters K-Means" y hacer clic');
print('           en zonas de alta favorabilidad (rojo/naranja)');
print('  PASO 3 → Anotar IDs de clusters → actualizar prospectiveClusters');
print('  PASO 4 → Ajustar umbral .gt(0.55) si necesitas más/menos área');
print('  PASO 5 → Ejecutar exportaciones desde pestaña Tasks');
print('  PASO 6 → QGIS/ArcGIS → superponer con geología regional');
print('  PASO 7 → Validar con capas de brechas conocidas');
print('══════════════════════════════════════════════════════');
print('⚠ NOTA: ASTER detecta halos de ALTERACIÓN (proxies de Au).');
print('  La confirmación de oro requiere muestreo geoquímico de campo.');
print('══════════════════════════════════════════════════════');
Cómo leer el diagnóstico en consola
①
Escenas con fecha 2000–2008 y nubes <30%
Si es mayor a 0, todo sigue normal. Si es 0, el script reintenta automáticamente.
②
Reintento sin filtro de nubes
Confirma si el problema era el umbral de nubes, no la disponibilidad real de escenas.
③
Reintento con todo el archivo ASTER (sin límite de fecha)
Si esto también da 0, el mensaje final te lo dice explícitamente: no hay ninguna escena ASTER sobre ese polígono — no es un bug, es ausencia de dato fuente.
🔬
Advertencia científica fundamental
Este sistema detecta halos de alteración hidrotermal — proxies indirectos de mineralización aurífera. La presencia de alunita, sílice o caolinita no confirma oro económico. La confirmación requiere muestreo geoquímico (Au en roca/suelo) y, eventualmente, perforación exploratoria. Es una herramienta de priorización para el trabajo de campo, no un detector directo de oro.
Anexo — Fin del curso ✓