# Guía de publicación

Procedimiento completo para pasar de los rásteres del artículo a datos
publicados y servidos por el visor. Los pasos 1–2 no requieren credenciales;
los pasos 3–5 sí.

## 0. Preparar el entorno

```bash
pip install -r tools/requirements.txt   # pipeline de datos
pnpm install                            # librerías del visor (solo si se actualizan)
```

Para ver el sitio en local hay que usar un servidor con soporte de solicitudes
parciales. `python -m http.server` **no** lo tiene y el mapa quedaría vacío:

```bash
pnpm run serve      # http://localhost:8000
```

## 1. Procesar los rásteres

Coloque los 24 GeoTIFF como `tn/01.tif … 12.tif` y `tx/01.tif … 12.tif` dentro
del directorio indicado por `source_dir` en `tools/config.yml`, y ajuste en ese
mismo archivo:

- `resolution_m: 250` y `expected.resolution` al tamaño de píxel real;
- `expected.epsg` y `expected.nodata` a los de sus rásteres;
- `synthetic: false`;
- `cog_dir` a un directorio **fuera del repositorio** (por ejemplo `dist/v1`),
  ya que los COG reales no deben versionarse.

Después:

```bash
python tools/01_validate_sources.py    # CRS, resolución, extensión, nodata, rango
python tools/02_make_cogs.py           # conversión y validación COG
python tools/03_build_stac.py          # checksums + STAC + manifiesto del visor
python tools/validate_catalog.py       # coherencia final (añada --schemas si hay red)
```

Si algún script termina con error, no continúe: los tres primeros están
encadenados y `03` depende del inventario que produce `01`.

Para regenerar las capas sintéticas de demostración basta con
`python tools/00_make_synthetic.py` antes del paso `01`.

## 2. Preparar el dataset horario

> El XLSX ya no está en el repositorio: con 38,4 MiB superaba el límite de
> **25 MiB por archivo** de Cloudflare Pages y hacía fallar el despliegue
> completo del sitio. Recupérelo del historial con
> `git show <commit>:TMED_HORARIO.xlsx > TMED_HORARIO.xlsx` o use su copia
> local, y pase la ruta con `--xlsx`.

```bash
python tools/convert_tmed.py --xlsx /ruta/a/TMED_HORARIO.xlsx
```

Produce en `work/dataset/` los formatos abiertos que acompañarán al Excel
original en Zenodo, y verifica que el Parquet reproduce el contenido del XLSX:

| Archivo | Tamaño |
| --- | --- |
| `TMED_HORARIO.xlsx` | 38,4 MB |
| `TMED_HORARIO.csv.gz` | 14,5 MB |
| `TMED_HORARIO.parquet` | 12,0 MB |

Son 175 320 registros horarios × 47 estaciones (2001–2020).

> Antes de redistribuir el dataset fuera de este repositorio, confirme los
> términos aplicables con los autores y con SIAM–IMIDA como fuente original.

## 3. Depositar en Zenodo

Zenodo es la copia citable: asigna un DOI propio al conjunto de datos,
independiente del DOI del artículo, y conserva cada versión de forma inmutable.

1. Inicie sesión en <https://zenodo.org> y cree un nuevo *upload* de tipo
   **Dataset**.
2. Suba `TMED_HORARIO.xlsx`, `TMED_HORARIO.csv.gz`, `TMED_HORARIO.parquet`, los
   24 COG y el directorio `stac/` comprimido.
3. Metadatos recomendados:
   - **Título**: Monthly air temperature climatologies (TN/TX) for the Region of
     Murcia from MODIS, 2001–2019.
   - **Autores**: los cuatro del artículo, con ORCID si está disponible.
   - **Licencia**: CC BY-NC-SA 4.0.
   - **Related identifiers**: `is supplement to` → `10.4995/raet.2023.18909`.
   - **Grants**: FONDECYT 1161809.
4. Publique y anote el DOI resultante.
5. Sustituya los marcadores: `dataset_doi` en `tools/config.yml` y el
   comentario `TODO-PUBLICACION` de la sección Datos en `index.html`. Localice
   todos con `grep -rn TODO-PUBLICACION .`
6. Regenere el catálogo (`python tools/03_build_stac.py`) para que el DOI quede
   incrustado en los metadatos STAC.

## 4. Publicar en Cloudflare R2

R2 sirve los COG al visor sin coste de salida de datos, que es lo que hace
viable exponer cartografía pesada en un proyecto sin presupuesto.

**La infraestructura ya está aprovisionada.** No hay que crear nada en el
panel; queda únicamente subir los objetos.

| Recurso | Valor |
| --- | --- |
| Bucket | `agromurcia-data` (región ENAM, clase Standard) |
| Dominio público | `https://datos-agromurcia.gfuentes.cl` |
| Prefijo de datos | `v1/{tn,tx}/NN.tif` |
| CORS | configurado (ver abajo) |

1. **R2 → Manage API tokens → Create API token**, permiso *Object Read & Write*
   limitado a `agromurcia-data`. Guarde `Access Key ID`, `Secret Access Key` y
   el *endpoint* `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`.
2. Configure `rclone` (las credenciales viven fuera del repositorio):

   ```ini
   # ~/.config/rclone/rclone.conf
   [r2]
   type = s3
   provider = Cloudflare
   access_key_id = ...
   secret_access_key = ...
   endpoint = https://<ACCOUNT_ID>.r2.cloudflarestorage.com
   acl = private
   ```

3. Suba el prefijo versionado:

   ```bash
   rclone sync dist/v1 r2:agromurcia-data/v1 --progress \
     --header-upload "Cache-Control: public, max-age=31536000, immutable"
   rclone sync stac r2:agromurcia-data/stac --progress \
     --header-upload "Cache-Control: public, max-age=3600"
   ```

4. Verifique la publicación:

   ```bash
   python tools/04_verify_remote.py \
     --base-url https://datos-agromurcia.gfuentes.cl/v1 \
     --origin https://agromurcia.gfuentes.cl --full
   ```

5. Apunte el visor al bucket: cambie `base_url` en `data/layers.json` (o
   `public_base_url` en `tools/config.yml` y regenere). A partir de ahí puede
   retirar `demo-data/` del repositorio.

6. Suba también la serie horaria, que no puede servirse desde Pages por el
   límite de 25 MiB:

   ```bash
   rclone copy work/dataset r2:agromurcia-data/dataset --progress
   rclone copy TMED_HORARIO.xlsx r2:agromurcia-data/dataset --progress
   ```

   Después reactive la descarga en la sección Datos de `index.html`, apuntando
   a `https://datos-agromurcia.gfuentes.cl/dataset/TMED_HORARIO.xlsx` (o
   preferiblemente al DOI de Zenodo).

7. Borre el objeto de prueba que quedó al validar el bucket:
   `rclone delete r2:agromurcia-data/_smoke-test.txt`

### Política CORS aplicada

Sitio y datos viven en subdominios distintos, así que el navegador exige CORS.
Esta es la política ya activa en el bucket:

```json
{
  "rules": [
    {
      "allowed": {
        "origins": [
          "https://agromurcia.gfuentes.cl",
          "https://agromurcia.pages.dev",
          "https://*.agromurcia.pages.dev",
          "http://localhost:8000"
        ],
        "methods": ["GET", "HEAD"],
        "headers": ["Range", "If-Match", "If-None-Match", "Content-Type"]
      },
      "exposeHeaders": ["ETag", "Content-Length", "Content-Range", "Accept-Ranges", "Content-Type"],
      "maxAgeSeconds": 86400
    }
  ]
}
```

`exposeHeaders` es imprescindible: sin `Content-Range` ni `Accept-Ranges`
visibles para el navegador, la lectura progresiva de los COG falla aunque el
objeto sea accesible. El comodín `*.agromurcia.pages.dev` cubre las URLs de
*preview* que Pages genera por rama y por commit.

## 5. Desplegar en Cloudflare Pages

**El proyecto ya está creado y conectado al repositorio.**

| Recurso | Valor |
| --- | --- |
| Proyecto | `agromurcia` |
| Subdominio Pages | `https://agromurcia.pages.dev` |
| Dominio propio | `https://agromurcia.gfuentes.cl` |
| Rama de producción | `main` |
| Previews | todas las ramas, con comentario automático en cada PR |
| Compilación | sin comando de build; raíz del repositorio como salida |

Cada `push` a `main` publica en producción; cada rama obtiene su propia URL de
preview. No hay nada que configurar por despliegue.

El archivo `_headers` de la raíz aplica caché y cabeceras de seguridad
automáticamente; no hay nada que configurar en el panel.

GitHub Pages puede desactivarse o dejarse como espejo: el sitio no depende de
Jekyll ni de ninguna característica específica de esa plataforma.

### Certificados

Ni Pages ni R2 usan el certificado Universal SSL de la zona: ambos emiten
certificados de Cloudflare for SaaS por hostname exacto. Por eso los dos
dominios se aprovisionan solos en plan gratuito, y un subdominio de tercer
nivel (`datos.agromurcia.gfuentes.cl`) también habría funcionado sin necesidad
de Advanced Certificate Manager. Tras crearlos, el estado tarda unos minutos en
pasar de *initializing*/*pending* a *active*.

## Notas de operación

- **404 en `*.tif.ovr`**: georaster sondea con `HEAD` la existencia de
  pirámides externas antes de usar las internas. El 404 es esperado y no
  afecta al renderizado.
- **Rollback**: como los prefijos son inmutables, basta con devolver
  `base_url` a la versión anterior; no hay que borrar nada.
- **Actualizar las librerías del visor**: `pnpm update && pnpm run vendor`.
  Nunca edite `vendor/` a mano.
