From 53f24de3ce5c3929e1f0d399246a0e0d8cc8e7c7 Mon Sep 17 00:00:00 2001 From: itziarZG Date: Tue, 14 Jul 2026 09:52:12 +0200 Subject: [PATCH] chore: estructura inicial del proyecto --- .gitignore | 66 ++++++++++++ FASE-0-setup.md | 126 +++++++++++++++++++++++ FASE-1-datos.md | 106 +++++++++++++++++++ FASE-2-web-mvp.md | 152 ++++++++++++++++++++++++++++ FASE-3-lanzamiento.md | 122 ++++++++++++++++++++++ PLAN.md | 59 +++++++++++ README.md | 49 +++++++++ data/raw/ORIGEN.md | 33 ++++++ docs/conferencia-OHIB-2025/Info.pdf | Bin 0 -> 36632 bytes requirements.txt | 8 ++ scripts/.gitkeep | 0 web/.gitkeep | 0 12 files changed, 721 insertions(+) create mode 100644 .gitignore create mode 100644 FASE-0-setup.md create mode 100644 FASE-1-datos.md create mode 100644 FASE-2-web-mvp.md create mode 100644 FASE-3-lanzamiento.md create mode 100644 PLAN.md create mode 100644 README.md create mode 100644 data/raw/ORIGEN.md create mode 100644 docs/conferencia-OHIB-2025/Info.pdf create mode 100644 requirements.txt create mode 100644 scripts/.gitkeep create mode 100644 web/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0acfe8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Datos crudos (no commitear por tamaño) +data/raw/*.xlsx +data/raw/*.csv +data/raw/*.csv.gz +data/raw/*.zip +data/raw/*.shp +data/raw/*.shx +data/raw/*.dbf +data/raw/*.prj +data/raw/*.cpg +data/raw/*.sbn +data/raw/*.sbx +data/raw/*.fbn +data/raw/*.fbx +data/raw/*.ain +data/raw/*.aih +data/raw/*.atx +data/raw/*.ixs +data/raw/*.mxs +data/raw/*.qix +data/raw/*.qpj +data/raw/*.xml + +# Capturas de la conferencia (grandes, locales unicamente) +docs/**/*.jpg +docs/**/*.JPG +docs/**/*.jpeg +docs/**/*.png + +# Procesado (regenerable desde raw con scripts/) +data/processed/ +data/output/ + +# Node +node_modules/ +.pnpm-store/ +dist/ +.astro/ +.vercel/ +*.log + +# Python +.venv/ +venv/ +__pycache__/ +*.py[cod] +*.egg-info/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# Entorno +.env +.env.local +.env.*.local + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# IDE +.vscode/ +.idea/ +*.swp +*.swo diff --git a/FASE-0-setup.md b/FASE-0-setup.md new file mode 100644 index 0000000..3284011 --- /dev/null +++ b/FASE-0-setup.md @@ -0,0 +1,126 @@ +# FASE 0 - Setup (semana 1) + +## Objetivo + +Tener el entorno listo y los datos crudos descargados y validados, sin procesar. Al cerrar esta fase, `git status` muestra una estructura limpia y `data/raw/` contiene los datasets originales descargados, cada uno abrible sin errores. + +## Tareas + +### Estructura de carpetas +- [ ] Crear en `/Users/ichi/Desktop/DEV/VIvienda/`: + - `data/raw/` - datos originales, inmutables + - `data/processed/` - datasets intermedios + - `data/output/` - parquet, tiles, JSONs finales + - `web/` - codigo Astro + - `docs/` - PDFs, capturas, metodologia extendida + - `scripts/` - pipeline Python + +### Repositorio +- [ ] `git init` en `/Users/ichi/Desktop/DEV/VIvienda/` +- [ ] Crear `.gitignore` que ignore: + - `data/raw/*.xlsx` y `data/raw/*.csv.gz` (datos pesados) + - `data/processed/` + - `data/output/` + - `node_modules/` + - `.venv/`, `__pycache__/` + - `.env`, `.env.local` +- [ ] Hacer commit inicial con estructura + `.gitignore` + este PLAN +- [ ] (Opcional) crear repo en GitHub y vincular + +### Reorganizacion de archivos existentes +- [ ] Mover `Info.pdf` y los 19 `.jpg` de la conferencia a `docs/conferencia-OHIB-2025/` +- [ ] Mover `viviendas vacias-hipotecas.xlsx` y `39365(1).xlsx` a `data/raw/` +- [ ] Documentar el origen de cada archivo en `data/raw/ORIGEN.md` + +### Entorno Python +- [ ] Instalar `uv` (gestor de entornos y paquetes rapido) +- [ ] `uv venv .venv` en la raiz del proyecto +- [ ] Crear `requirements.txt` con: pandas, openpyxl, geopandas, duckdb, shapely, pyproj, fiona, pyarrow +- [ ] `uv pip install -r requirements.txt` +- [ ] Verificar: `python -c "import geopandas, duckdb; print('OK')"` + +### Entorno Node +- [ ] Instalar Node 20+ (con `nvm` o `brew`) +- [ ] Instalar pnpm (`npm install -g pnpm`) + +### Descarga de datos externos +- [ ] Descargar **shapefile de secciones censales de Balears** desde INE + - Fuente: https://www.ine.es/prodyser/cartografia/seccionado_2025/ + - Descargar las 4 provincias: Mallorca (07), Menorca (07), Eivissa (07), Formentera (07) + - Verificar que incluye geometria poligonal +- [ ] Descargar **Inside Airbnb listings** para las 4 islas + - Fuente: http://insideairbnb.com/get-the-data/ + - Snapshot mas reciente disponible para: Mallorca, Menorca, Eivissa, Formentera + - Formato: `listings.csv.gz` (revisar tamano y fecha) + +### Validacion +- [ ] Shapefile abre con geopandas, tiene CRS correcto, conteo de features > 0 +- [ ] Listings CSV abre con pandas, columnas esperadas presentes +- [ ] XLSXs del usuario abren con openpyxl/pandas +- [ ] Anotar numero de filas de cada dataset en `data/raw/VALIDACION.md` + +### Cierre +- [ ] Commit final con tag `fase-0-completa` +- [ ] Actualizar PLAN.md: marcar Fase 0 como completada +- [ ] Anotar problemas y decisiones en este documento + +## Comandos clave + +```bash +# Estructura +cd /Users/ichi/Desktop/DEV/VIvienda +mkdir -p data/raw data/processed data/output web docs/conferencia-OHIB-2025 scripts + +# Git +git init +git add . +git commit -m "chore: estructura inicial del proyecto" + +# Python +brew install uv # o pipx install uv +uv venv .venv +source .venv/bin/activate +uv pip install pandas openpyxl geopandas duckdb shapely pyproj fiona pyarrow + +# Node +brew install node pnpm # o nvm install 20 && npm i -g pnpm + +# Validacion shapefile (despues de descargar) +python -c "import geopandas as gpd; gdf = gpd.read_file('data/raw/secciones_censales_balears.shp'); print(gdf.head()); print('CRS:', gdf.crs); print('Features:', len(gdf))" + +# Validacion listings +python -c "import pandas as pd; df = pd.read_csv('data/raw/eivissa_listings.csv.gz', low_memory=False); print(df.shape); print(df.columns.tolist()[:20])" + +# Validacion XLSX +python -c "import pandas as pd; df = pd.read_excel('data/raw/viviendas vacias-hipotecas.xlsx', sheet_name=None); print({k: v.shape for k, v in df.items()})" +``` + +## Criterio de "fase terminada" (Definition of Done) + +- [x] Repo git inicializado con branch `main` +- [x] Estructura de carpetas creada +- [x] `.gitignore` funcionando (verificar con `git status`) +- [x] Entorno Python reproducible: `requirements.txt` con versiones pinneadas + instrucciones +- [x] Entorno Node instalado (Node 20+ + pnpm) +- [x] `data/raw/` contiene: 2 XLSXs INE + shapefile secciones censales Balears + listings Airbnb (4 archivos) +- [x] Cada archivo abre sin error y tiene el numero esperado de filas/features +- [x] `data/raw/VALIDACION.md` documenta conteos y CRS +- [x] Commit final con tag `fase-0-completa` +- [x] PLAN.md actualizado con check de Fase 0 + +## Decisiones tomadas durante la fase + +_Llenar durante la ejecucion. Ejemplos:_ +- _Decidimos usar `uv` en vez de `poetry` porque…_ +- _El shapefile viene en ETRS89 (EPSG:4258), no WGS84, hay que reproyectar a EPSG:4326 para web._ +- _Decidimos ignorar `data/raw/*.xlsx` en git por tamano; el repo solo guarda scripts y docs._ + +## Problemas encontrados + +_Llenar durante la ejecucion. Ejemplos:_ +- _El shapefile de Formentera viene en un ZIP separado y CRS distinto al de Mallorca._ +- _Inside Airbnb no tiene snapshot para Formentera separado; el de Eivissa incluye ambos._ + +## Proximos pasos + +Fase 1: pipeline de datos Python. Entrada: archivos en `data/raw/`. Salida: `dataset.parquet` y `tiles.pmtiles` en `data/output/`. Ver `FASE-1-datos.md`. diff --git a/FASE-1-datos.md b/FASE-1-datos.md new file mode 100644 index 0000000..7ac63f5 --- /dev/null +++ b/FASE-1-datos.md @@ -0,0 +1,106 @@ +# FASE 1 - Datos (semana 2) + +## Objetivo + +Producir el dataset final agregado por seccion censal con dos variables clave: (a) % de viviendas semi-vacias estimado por consumo electrico, y (b) numero/densidad de listings de Airbnb. Generar archivos listos para la web: `dataset.parquet` (tabla) y `tiles.pmtiles` (visualizacion geoespacial). + +## Tareas + +### Exploracion y limpieza +- [ ] Cargar `viviendas vacias-hipotecas.xlsx`, inspeccionar las 6 sheets, identificar la que tiene datos por seccion censal +- [ ] Filtrar registros de Illes Balears (codigos INE 07xxx) +- [ ] Normalizar columnas: codigo seccion censal, % viviendas semi-vacias, % viviendas vacias, consumo mediano kWh +- [ ] Cargar `39365(1).xlsx` (tabla 39365 viviendas turisticas), agregar a Balears como dato contextual + +### Carga geoespacial +- [ ] Cargar shapefile de secciones censales Balears con geopandas +- [ ] Reproyectar a WGS84 (EPSG:4326) si esta en ETRS89 u otro CRS +- [ ] Inspeccionar: codigo unico de seccion censal, geometria valida, total de features + +### Inside Airbnb +- [ ] Cargar los 4 CSVs de listings (Mallorca, Menorca, Eivissa, Formentera) +- [ ] Columnas clave: `id`, `latitude`, `longitude`, `room_type`, `availability_365`, `price`, `neighbourhood` +- [ ] Filtrar listings activos: `availability_365 > 0` y/o `room_type == 'Entire home/apt'` +- [ ] Limpiar precios (quitar `$`, `,`, convertir a float) + +### Spatial join +- [ ] Point-in-polygon: cada listing -> seccion censal correspondiente +- [ ] Manejar listings fuera de poligonos (zonas rurales, agua): log + excluir +- [ ] Resultado: DataFrame con `(seccion_censal, n_listings)` por isla + +### Agregacion por seccion censal +- [ ] Join: seccion_censal <- (datos INE vivienda) + (conteo Airbnb) +- [ ] Variables finales por seccion censal: + - `cod_seccion` (PK) + - `municipio`, `isla` + - `n_viviendas_total` + - `pct_viviendas_vacias` + - `pct_uso_esporadico` + - `consumo_mediano_kwh` + - `n_listings_airbnb` + - `listings_por_100_viviendas` + - `geometry` (poligono) +- [ ] Guardar como `data/output/dataset.parquet` (formato columnar eficiente) + +### Tiles para mapa web +- [ ] Generar `data/output/tiles.pmtiles` con tippecanoe a partir del GeoPackage del dataset +- [ ] Incluir atributos de visualizacion en los tiles (no solo geometria) +- [ ] Generar version "minima" de tiles (zoom 0-12) para web + +### Validacion +- [ ] `dataset.parquet` abre con duckdb/pandas +- [ ] `tiles.pmtiles` se sirve correctamente y se ve en MapLibre local +- [ ] Sanity checks: nº listings total razonable (>5.000 en Mallorca, >1.000 en Eivissa), sin nulos en columnas clave +- [ ] Documentar estadisticas descriptivas en `data/output/STATS.md` + +### Cierre +- [ ] Commit final con tag `fase-1-completa` +- [ ] Actualizar PLAN.md +- [ ] Visualizar el mapa en un Jupyter notebook rapido para validar (screenshot al menos) + +## Comandos clave + +```bash +# Convertir a GeoPackage para tippecanoe +ogr2ogr -f GPKG data/output/dataset.gpkg data/output/dataset.parquet + +# Generar tiles +tippecanoe -o data/output/tiles.pmtiles \ + -zg \ + --drop-densest-as-needed \ + -l cases \ + --read-parallel \ + data/output/dataset.gpkg + +# Servir localmente para testear +python -m http.server 8000 --directory data/output +# Abrir http://localhost:8000 en navegador con MapLibre + +# Validar parquet con duckdb +python -c "import duckdb; print(duckdb.query('SELECT isla, COUNT(*), AVG(pct_uso_esporadico), SUM(n_listings_airbnb) FROM \"data/output/dataset.parquet\" GROUP BY isla').to_df())" +``` + +## Criterio de "fase terminada" + +- [x] `data/output/dataset.parquet` generado, valido, sin nulos en columnas clave +- [x] `data/output/tiles.pmtiles` generado y servible +- [x] Spatial join con tasa de exito > 95% (listings asignados a seccion censal) +- [x] `STATS.md` documenta: nº secciones, nº listings por isla, distribuciones, outliers +- [x] Screenshot del mapa en Jupyter notebook como evidencia visual +- [x] Scripts Python reproducibles en `scripts/` con comentarios sobre decisiones +- [x] Commit final con tag `fase-1-completa` + +## Decisiones tomadas durante la fase + +_Llenar durante la ejecucion. Ejemplos:_ +- _Decidimos filtrar listings por `availability_365 > 60` para excluir inactivos esporadicos._ +- _Decidimos calcular "pct_uso_esporadico" como 1 - (consumo_mediano / p75 consumo), no como categoria INE directa._ +- _El join espacial asigna por punto interior al poligono; los listings a < 50m del borde se asignan al mas cercano (regla documentada)._ + +## Problemas encontrados + +_Llenar durante la ejecucion._ + +## Proximos pasos + +Fase 2: web MVP con Astro + MapLibre. Ver `FASE-2-web-mvp.md`. diff --git a/FASE-2-web-mvp.md b/FASE-2-web-mvp.md new file mode 100644 index 0000000..21308be --- /dev/null +++ b/FASE-2-web-mvp.md @@ -0,0 +1,152 @@ +# FASE 2 - Web MVP (semanas 3-4) + +## Objetivo + +Web minima viable desplegada en URL temporal con 3 paginas funcionales: home con headline + `/mapa` interactivo + `/metodologia`. La web consume `dataset.parquet` y `tiles.pmtiles` generados en Fase 1. El objetivo es validar la experiencia de usuario y la narrativa antes de invertir en contenido adicional. + +## Tareas + +### Inicializacion del proyecto web +- [ ] `pnpm create astro@latest web/` con template "basics" (TypeScript: strict) +- [ ] Integrar Tailwind CSS (configuracion por defecto) +- [ ] Integrar MapLibre GL JS como dependencia +- [ ] Configurar estructura de paginas: `src/pages/index.astro`, `mapa.astro`, `metodologia.astro` +- [ ] Configurar layout comun en `src/layouts/Base.astro` (header + footer + meta tags) + +### Datos +- [ ] Copiar `tiles.pmtiles` a `web/public/tiles/` +- [ ] Crear `web/public/data/dataset.json` (version simplificada del parquet para stats del home y `/metodologia`) +- [ ] Definir schema: `dataset.json` con stats agregadas por isla (4 entradas) y headline stats globales + +### Pagina `/` (home) +- [ ] Hero section con headline + cifra impacto (ej: "El X% de las viviendas de Balears estan semi-vacias") +- [ ] Cifra destacada 2: nº total de listings de Airbnb en Balears +- [ ] Mini-mapa estatico (PNG exportado) con CTA "Ver mapa interactivo" +- [ ] Explicacion breve (3 parrafos) de que es el proyecto +- [ ] CTA a `/mapa` + +### Pagina `/mapa` (interactivo) +- [ ] MapLibre GL inicializado ocupando 100% del viewport +- [ ] Capa choropleth: seccion censal coloreada por `pct_uso_esporadico` +- [ ] Capa puntos: listings de Airbnb (clustered a bajo zoom) +- [ ] Toggle UI para cambiar entre capas / activar/desactivar +- [ ] Popup on-click de seccion censal con stats +- [ ] Sidebar con leyenda y stats principales +- [ ] Filtro por isla (dropdown: Mallorca, Menorca, Eivissa, Formentera) + +### Pagina `/metodologia` +- [ ] Explicacion de fuentes (INE, Inside Airbnb) con links +- [ ] Limitaciones: sesgo de Airbnb como cota inferior, granularidad seccion censal +- [ ] Formula de "vivienda semi-vacia" explicada +- [ ] Fecha de los datos (snapshot) +- [ ] Licencia del proyecto (CC BY-SA 4.0 recomendado) +- [ ] Contacto + +### Despliegue +- [ ] Crear cuenta en Vercel (free tier) +- [ ] `vercel link` y configurar como static site +- [ ] Primer deploy: URL `casestancades.vercel.app` (o el nombre que se elija) +- [ ] Verificar que el mapa carga tiles correctamente +- [ ] Compartir URL con 2-3 personas de confianza para feedback inicial + +### Cierre +- [ ] Commit final con tag `fase-2-completa` +- [ ] Actualizar PLAN.md +- [ ] Anotar feedback recibido en este documento + +## Comandos clave + +```bash +# Crear proyecto +cd /Users/ichi/Desktop/DEV/VIvienda +pnpm create astro@latest web -- --template basics --typescript strict --yes +cd web +pnpm install +pnpm add maplibre-gl pmtiles tailwindcss + +# Dev local +pnpm dev # http://localhost:4321 + +# Build +pnpm build + +# Deploy +pnpm dlx vercel +``` + +## Snippet: inicializar MapLibre con PMTiles + +```typescript +// web/src/components/Mapa.astro + + +
+``` + +## Criterio de "fase terminada" + +- [x] Web desplegada en URL temporal de Vercel +- [x] 3 paginas funcionales: home, /mapa, /metodologia +- [x] Mapa interactivo carga tiles y permite explorar Balears +- [x] Sin errores en consola del navegador +- [x] Mobile responsive (mapa con dedos funciona) +- [x] Feedback de al menos 1 persona ajena al proyecto +- [x] Commit final con tag `fase-2-completa` + +## Decisiones tomadas durante la fase + +_Llenar durante la ejecucion._ + +## Problemas encontrados + +_Llenar durante la ejecucion._ + +## Proximos pasos + +Fase 3: lanzamiento publico. Ver `FASE-3-lanzamiento.md`. diff --git a/FASE-3-lanzamiento.md b/FASE-3-lanzamiento.md new file mode 100644 index 0000000..f8c1c0a --- /dev/null +++ b/FASE-3-lanzamiento.md @@ -0,0 +1,122 @@ +# FASE 3 - Lanzamiento (semanas 5-6) + +## Objetivo + +Web publica completa con todas las paginas, contenido pulido, SEO basico, kit de prensa descargable y outreach a medios locales. Al cerrar esta fase, la web esta lista para ser compartida en redes y enviada a prensa. + +## Tareas + +### Paginas adicionales +- [ ] `/illes` - comparativa entre las 4 islas (Mallorca, Menorca, Eivissa, Formentera) con graficos de barras/lines + - Variables: % semi-vacias, densidad Airbnb, €/m² (dato conferencia OHIB) + - Mini-mapa por isla con su zoom automatico +- [ ] `/dades` - descarga de datos abiertos + - Link a `dataset.parquet` (raw) + - Link a `dataset.csv` (version simplificada) + - Scripts Python de pipeline con instrucciones + - Licencia CC BY-SA 4.0 +- [ ] `/premsa` - kit de prensa + - Cifras citables (1-2 frases, sin jerga) + - Graficos PNG exportables en alta resolucion + - Fotos libres de derecho (mapa) con atribucion + - Bio del proyecto + - Contacto (email) +- [ ] `/qui-som` - transparencia + - Quien esta detras, motivacion, declaracion de conflicto de interes (ninguno) + - Como contribuir (issues, PRs, donaciones) + +### Pulido tecnico +- [ ] SEO: meta description, OG tags, sitemap.xml, robots.txt +- [ ] Open Graph cards con imagen del mapa (1200x630) para compartir en redes +- [ ] Accesibilidad basica: contraste, alt text, navegacion por teclado +- [ ] Performance: lazy load de MapLibre, optimizar tamano de bundle +- [ ] Diseno final: revisar paleta de colores, tipografia, espaciado + +### Contenido +- [ ] Redactar textos en catalan como idioma principal +- [ ] Version en castellano (no automatico, traducir y adaptar) +- [ ] Disclaimer legal en pie de pagina +- [ ] Pagina de errores (404) con branding + +### Outreach +- [ ] Lista de medios locales: Diario de Ibiza, Ara Balears, La Marea, ElDiario.es Balears +- [ ] Lista de colectivos: PAH Ibiza, GOB, Observatori de l'Habitatge +- [ ] Email tipo para prensa (curado, no spam) +- [ ] Enviar email 1-2 semanas antes del lanzamiento publico + +### Dominio definitivo +- [ ] Decidir nombre final del proyecto +- [ ] Comprar dominio (`.cat`, `.es` o `.org`) +- [ ] Configurar en Vercel como dominio principal +- [ ] HTTPS automatico + +### Cierre +- [ ] Commit final con tag `fase-3-completa` +- [ ] Actualizar PLAN.md +- [ ] Hacer backup del repo y de los datos +- [ ] Documentar todo el proyecto en README.md + +## Comandos clave + +```bash +# Deploy con dominio propio +vercel domains add casestancades.cat + +# Generar sitemap (Astro plugin) +pnpm add @astrojs/sitemap + +# Lighthouse +pnpm dlx lighthouse https://casestancades.cat --view + +# Test mobile +# Abrir Chrome DevTools -> Device toolbar -> iPhone 14 Pro +``` + +## Snippet: meta tags Open Graph + +```astro +--- +// web/src/layouts/Base.astro +const { title, description } = Astro.props; +--- + + + + {title} | Cases Tancades + + + + + + + +``` + +## Criterio de "fase terminada" + +- [x] Las 7 paginas del plan implementadas (`/`, `/mapa`, `/metodologia`, `/illes`, `/dades`, `/premsa`, `/qui-som`) +- [x] Dominio definitivo configurado y HTTPS funcionando +- [x] Lighthouse score > 90 en Performance y Accessibility +- [x] Open Graph card valida (https://www.opengraph.xyz/) +- [x] Sitemap.xml generado +- [x] Email de prensa enviado a al menos 3 medios +- [x] Licencia y disclaimer visibles +- [x] README.md del proyecto con instrucciones para reproducir +- [x] Commit final con tag `lanzamiento-mvp` (o `v0.1.0`) + +## Decisiones tomadas durante la fase + +_Llenar durante la ejecucion._ + +## Problemas encontrados + +_Llenar durante la ejecucion._ + +## Proximos pasos (post-MVP) + +- Refresh trimestral del dataset (cron o manual) +- Captar feedback de usuarios y prensa +- Iterar visualizaciones segun datos que mas resuenen +- Explorar v2 con datos del registro HUT del Govern (mas completo que Inside Airbnb) +- Explorar alianza con OHIB, universidad o colectivo +- Captar financiacion (subvenciones periodismo de datos) si crece el proyecto diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..a0ff002 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,59 @@ +# Plan: Cases Buides / Tancades (Vivienda vacía + alquiler temporal en Balears) + +## 1. Resumen ejecutivo + +Web publica que cruza dos datos oficiales - INE (consumo electrico por seccion censal, como proxy de vivienda semi-vacia) + Inside Airbnb (listings georreferenciados) - para visibilizar la presion turistica sobre el parque residencial de Balears. Audiencia principal: ciudadania general ibicenca. Plazo objetivo: MVP publico en 4-6 semanas. Stack: Python (pipeline de datos) + Astro (web estatica) + MapLibre GL (mapas interactivos) + Vercel (hosting). + +## 2. Decisiones cerradas + +| Decision | Valor | Razon | +|---|---|---| +| Formato entregable | Web publica | Compartible, viralizable, accesible para prensa y ciudadania | +| Audiencia | Ciudadania general ibicenca | Lenguaje accesible, storytelling visual, foco local | +| Granularidad datos | Seccion censal (500-2.000 hogares) | INE solo llega a este nivel, defendible y honesto | +| Plazo total | 4-6 semanas (MVP) | Validar rapido, iterar despues | +| Datos vivienda vacia | Consumo electrico INE (ya disponible) | Unico dato publico oficial con cobertura islenya completa | +| Datos alquiler temporal | Inside Airbnb (datos abiertos) | Legal, reproducible, suficiente para cota inferior | +| Alcance geografico | Balears entero (4 islas) | Comparativa potente entre islas, reutiliza trabajo | +| Recursos | Solo, perfil tecnico dev | Plan realista sin dependencias externas | +| Estilo iteracion | Waterfall secuencial (4 fases) | Mantener foco, evitar scope creep | +| Documentacion | PLAN.md + 1 doc por fase | Modular, facil de iterar, auditable | + +## 3. Fases (resumen) + +- **Fase 0 (sem 1) - Setup.** Repo git, entorno Python/Node, descarga de shapefile de secciones censales Balears (INE) e Inside Airbnb listings para las 4 islas, mover XLSXs del usuario a `data/raw/`. +- **Fase 1 (sem 2) - Datos.** Pipeline Python: limpiar XLSX INE -> cargar shapefile -> cargar listings -> spatial join listings->seccion censal -> agregar dataset -> generar `dataset.parquet` y `tiles.pmtiles`. +- **Fase 2 (sem 3-4) - Web MVP.** Astro + MapLibre GL: home con headline + `/mapa` interactivo (capa semi-vacias + capa Airbnb) + `/metodologia`. Despliegue en Vercel con URL temporal. +- **Fase 3 (sem 5-6) - Lanzamiento.** `/illes` comparativa entre islas + `/dades` descarga de CSVs/parquet + `/premsa` kit de prensa + SEO basico + Open Graph cards + outreach a prensa local. + +## 4. Riesgos + +| Riesgo | Mitigacion | +|---|---| +| Inside Airbnb no se actualiza con frecuencia | Snapshot con fecha visible en `/metodologia`; documentar fuente alternativa (registro HUT del Govern) para v2 | +| Sesgo: no todos los listings estan en Airbnb | Comunicar en metodologia: es **cota inferior** de la presion turistica real | +| Granularidad seccion censal (no direccion exacta) | Reenmarcar narrativamente: "mapa por barrio de 500-2.000 hogares" en vez de portal exacto. Honesto y defendible. | +| Scraping / TOS de portales | NO scrappear Idealista/Airbnb directamente; usar solo datos abiertos (Inside Airbnb + INE/Catastro) | +| Dominio `.cat` ocupado o caro | Plan B: `.es`, `.org`, subdominio Vercel (`casestancades.vercel.app`) | +| Demanda legal por denuncia publica | Disclaimer "datos publicos, metodologia abierta, sin acusacion individual" en pie de pagina | +| Shapefile secciones censales no incluye Formentera como tal | Validar en Fase 0; si falta, trabajar con secciones censales de los 4 municipios de Eivissa + Formentera juntos | + +## 5. Estado actual + +- [x] Carpeta `/Users/ichi/Desktop/DEV/VIvienda/` identificada con datos del usuario +- [x] XLSXs INE descargados por el usuario (`viviendas vacias-hipotecas.xlsx`, `39365(1).xlsx`) +- [x] Capturas de conferencia OHIB (12-13 nov 2025) revisadas +- [x] Plan inicial creado y validado con el usuario +- [x] `PLAN.md` escrito +- [x] `FASE-0-setup.md` escrito (pendiente ejecucion) +- [ ] Fase 0 ejecutada +- [ ] Fase 1 ejecutada +- [ ] Fase 2 ejecutada +- [ ] Fase 3 ejecutada +- [ ] Web publica desplegada en dominio definitivo + +## 6. Notas de iteracion + +_Espacio para cambios futuros. Cada vez que se ajuste el plan, dejar nota con fecha y razon._ + +- **2026-07-13** - Plan inicial creado tras revision de datos del usuario y conferencia OHIB. Decisiones cerradas: web publica, ciudadania ibicenca, seccion censal, 4-6 semanas, Inside Airbnb, Balears entero, dev solo, waterfall, documentacion modular. diff --git a/README.md b/README.md new file mode 100644 index 0000000..029dc6c --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Cases Tancades + +Web publica que visibilitza la pressio turistica sobre el parc residencial de Balears, creuant dades oficiales d'INE (consum electric per seccio censal, com a proxy de vivenda semi-vacia) amb dades d'Inside Airbnb (listings georreferenciats). + +Veure [PLAN.md](PLAN.md) per al plan complet i l'arquitectura. + +## Estructura + +``` +data/raw/ Dades originals descarregades (no commitejades) +data/processed/ Dades intermiges del pipeline +data/output/ Dades finals (parquet, tiles, JSON) +docs/ Documentacio i materials de suport +scripts/ Pipeline Python +web/ Codi Astro de la web +FASE-*.md Bitacora de cada fase del projecte +``` + +## Setup rapid + +```bash +# Python +uv venv .venv +source .venv/bin/activate +uv pip install -r requirements.txt + +# Node +nvm use 20 +``` + +## Estat + +| Fase | Estat | +|---|---| +| Fase 0 - Setup | En curs | +| Fase 1 - Dades | Pendent | +| Fase 2 - Web MVP | Pendent | +| Fase 3 - Llanament | Pendent | + +## Fonts de dades + +- INE - Intensitat d'us per consum electric: taula del fitxer `data/raw/viviendas vacias-hipotecas.xlsx` (full 1). +- INE - Seccions censals de Balears: shapefile descarregat de https://www.ine.es/prodyser/cartografia/seccionado_2025/ +- Inside Airbnb - Listings Balears: http://insideairbnb.com/get-the-data/ +- Captures conferencia OHIB (12-13 nov 2025): `docs/conferencia-OHIB-2025/` + +## Llicencia + +A determinar. Probablement CC BY-SA 4.0. diff --git a/data/raw/ORIGEN.md b/data/raw/ORIGEN.md new file mode 100644 index 0000000..353cfdd --- /dev/null +++ b/data/raw/ORIGEN.md @@ -0,0 +1,33 @@ +# Origen de las dades en `data/raw/` + +## `viviendas vacias-hipotecas.xlsx` + +- **Origen**: Instituto Nacional de Estadistica (INE). Tabla derivada del "Censo de poblacion y viviendas" + datos de consumo electrico. +- **URL de descarga**: https://www.ine.es (tabla 39365 + tabla especifica d'intensitat d'us) +- **Fecha de descarga**: 2025 (proporcionado por el usuario del proyecto) +- **Descripcion**: 6 fulles. La full 1 conte dades d'intensitat d'us de les vivendes segons consum electric per seccio censal. Inclou Total Nacional + Illes Balears (provincies + seccions). +- **Tamano**: 35 KB + +## `39365(1).xlsx` + +- **Origen**: Instituto Nacional de Estadistica (INE). Tabla 39365 - "Viviendas turisticas en Espana". +- **URL de descarga**: https://www.ine.es/jaxiT3/Tabla.htm?t=39365 +- **Fecha de descarga**: 2025 (proporcionado por el usuario del proyecto) +- **Descripcion**: 1 full. Serie temporal 2020M08 - 2025M05. Percentage de vivendes turistiques sobre el total per comunitat autonoma. +- **Tamano**: 10 KB + +## Shapefile de secciones censales de Balears + +- **Origen**: Instituto Nacional de Estadistica (INE). Cartografia censal. +- **URL de descarga**: https://www.ine.es/prodyser/cartografia/seccionado_2025/ +- **Fecha de descarga**: (pendiente, ejecutar en setup de Fase 0) +- **Descripcion**: Poligons de les seccions censals per a les Illes Balears. +- **CRS original**: ETRS89 (EPSG:4258), reprojectar a WGS84 (EPSG:4326) per a la web. + +## Inside Airbnb listings + +- **Origen**: Inside Airbnb (proyecto independiente que scrapea Airbnb y publica datos abiertos). +- **URL de descarga**: http://insideairbnb.com/get-the-data/ +- **Fecha de descarga**: (pendiente, ejecutar en setup de Fase 0) +- **Descripcion**: Listings georreferenciats (lat/lon) amb informacio de tipus, disponibilitat i preu. Snapshots separats per Mallorca, Menorca, Eivissa i Formentera. +- **Licencia**: Creative Commons (consultar web del proyecto). diff --git a/docs/conferencia-OHIB-2025/Info.pdf b/docs/conferencia-OHIB-2025/Info.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e0abef1d70355a879e2c6941b7dfa7680d84f854 GIT binary patch literal 36632 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 TE9iiqJlFs*q@==2.2 +openpyxl>=3.1 +pyarrow>=16.0 +duckdb>=1.0 +geopandas>=1.0 +shapely>=2.0 +pyproj>=3.6 +fiona>=1.10 diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/web/.gitkeep b/web/.gitkeep new file mode 100644 index 0000000..e69de29