SPAB — Clustering: resultado, y por qué el vector del plan no servía

Fecha: 2026-09-14 · Proyecto: ~/nef/estado_turismo/spab-retrieval Cierra el pendiente #15 (“Clustering KMeans + jerárquico”). Reemplaza la sección 4 de 2026-09-14-spab-plan-kmeans.md.

1. Lo que corrió

scriptqué hacesalida
scripts/clustering.pyK-means snapshot (plan original, 4 variantes)data/modelos/clustering.md
scripts/clustering_estacional.pyK-means sobre perfil estacionaldata/modelos/estacionalidad.md
scripts/exportar_base.pyconsolida obs/*.ndjson → base/observaciones.csv con dedup—

Semilla fija 20260914, n_init=20, Ward como contraste, estabilidad por bootstrap (200 remuestreos, ARI contra el ajuste completo).

2. El snapshot NO tiene estructura — resultado negativo, documentado

El vector v1 del plan (precio, rating, reviews_conteo, menciones_caro, vistas_wikipedia) produce un clúster degenerado:

varianteksiluetaqué separa
v1 crudo20.52CDMX sola — silueta inflada por singleton
v1 log10 en conteos20.41CDMX sola otra vez
v1 log, sin CDMX20.27ruido
v2 log, sin CDMX (+ocupación)20.21ruido

Causa. Sin CDMX, la varianza útil no existe:

featureCV (σ/μ) sin CDMX
rating0.031 — todos entre 4.23 y 4.70
precio_hospedaje0.136
menciones_caro0.265
vistas_wikipedia0.378
reviews_conteo0.642

Con CDMX dentro, el k-means detecta tamaño de ciudad, no turismo: CDMX tiene 10× las vistas de Wikipedia y 5× las reseñas de la mediana. Las features del snapshot miden popularidad y percepción genérica, no saturación.

Se conserva como línea base negativa — es evidencia para el criterio 4 de la rúbrica (“valida, compara y justifica la selección”): se probó, se midió, se descartó con métrica.

3. El perfil estacional SÍ tiene estructura — este es el resultado

Vector = índice estacional: ocupación(mes) / ocupación media anual del destino, 12 features. Escala-invariante a propósito: agrupa por forma de la curva, no por nivel. Fuente DataTur (canónica), 2023-01→2026-06, mínimo 24 meses por destino.

k = 3 · silueta 0.370 · Davies-Bouldin 0.58 · estabilidad ARI 0.821 · acuerdo con Ward ARI 1.000 (los dos métodos dan la misma partición).

clústerdestinosperfilamplitud
0 — playa de inviernocancún, playa del carmen, puerto vallarta, puerto escondidopico feb/ene, valle sep0.40
1 — ciudad / culturalcdmx, oaxaca, san miguel de allendepico nov/oct, valle jun/ene0.33
2 — nacional de puenteacapulcopico abr (Semana Santa), valle oct0.63

Acapulco queda solo (silueta 0.0) y es correcto, no artefacto: es el perfil más estacional del panel y el único que depende de vacaciones nacionales en vez de invierno del norte.

4. Qué implica para la campaña

  1. El valle es septiembre-octubre y es compartido por todo el clúster playa (Cancún 0.86, Playa del Carmen 0.73, Puerto Escondido 0.66). Ahí está la capacidad ociosa: redistribuir en el tiempo antes que en el espacio.
  2. Los clústeres 0 y 1 son parcialmente anticíclicos: el clúster ciudad pica en oct/nov, justo cuando la playa toca fondo. Sirve para el diseño de escenarios (criterio 9).
  3. Bacalar y Tulum siguen sin ocupación DataTur — los dos ejes del plan son los dos huecos. Es el pendiente #4 y ahora bloquea el resultado principal, no un extra.

5. Bug corregido de paso

src/lib/store.ts documentaba que leerObservaciones se quedaba “con la captura más reciente por (destino, periodo, métrica)” — no lo hacía. Cada re-ingesta duplicaba todo. La base tenía 3,160 filas donde hay 1,675 observaciones reales (1,485 duplicados, 47%).

Afectaba a api/burbuja, page.tsx y destino/[slug]: el índice de burbuja se calculaba sobre duplicados. Corregido con colapso en lectura ({ historial: true } para quien quiera el histórico crudo). tsc limpio.

6. Siguiente

  1. Ocupación de Bacalar y Tulum — deja de ser pendiente cómodo. Proxy “Riviera Maya” para Tulum; para Bacalar, decidir entre proxy Chetumal o declararlo fuera del análisis de ocupación.
  2. Reponer el clustering estacional con llegadas_turistas (serie 2012→2026, 14 años — permite detectar cambio de perfil, no sólo perfil).
  3. SESNSP sigue faltando; ya no bloquea este resultado.