chore: initial sync

This commit is contained in:
fn-registry agent
2026-04-28 22:13:09 +02:00
commit ea833af54c
13 changed files with 13989 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# JUPYTER HABILITADO EN ESTE ANALISIS
## Reglas OBLIGATORIAS para Claude
### 1. CODIGO INMUTABLE — NUNCA MODIFICAR CELDAS EXISTENTES
- **PROHIBIDO** usar NotebookEdit para reemplazar celdas existentes
- **SIEMPRE** anadir celdas NUEVAS al final del notebook
- Si hay un error en una celda, crear celda nueva con la correccion
- El historial de trabajo debe quedar intacto para trazabilidad
### 2. PROGRAMACION FUNCIONAL OBLIGATORIA
- **Funciones puras**: sin efectos secundarios, mismo input -> mismo output
- **Inmutabilidad**: nunca mutar datos, crear copias transformadas
- **Composicion**: funciones pequenas que se combinan
- Preferir: `map`, `filter`, `reduce`, list comprehensions
- Evitar: loops con mutacion, `global`, modificar argumentos in-place
### 3. SIEMPRE usar MCP jupyter para ejecutar codigo Python
- Las ejecuciones se ven en tiempo real en Jupyter Lab del usuario
- Compartimos variables y estado del kernel
- **NUNCA usar bash para ejecutar Python en este analisis**
### 4. Verificar Jupyter activo ANTES de ejecutar
- Si no esta activo: pedir al usuario que ejecute `./run-jupyter-lab.sh`
### 5. Gestion de notebooks
- Notebooks en la carpeta `notebooks/` o subcarpetas
- Si un notebook tiene >50 celdas, crear uno nuevo
- Nombrar descriptivamente: `01_exploracion.ipynb`, `02_limpieza.ipynb`
### 6. Gestion de Python
- **SIEMPRE usar `uv`** para gestionar dependencias
- Anadir paquetes con `uv add nombre_paquete`
### 7. Acceso al fn_registry
- `FN_REGISTRY_ROOT` apunta a la raiz del registry
- Para importar funciones Python: `sys.path.insert(0, os.path.join(os.environ["FN_REGISTRY_ROOT"], "python", "functions"))`
- Para consultar registry.db: `sqlite3` o `import sqlite3` con la ruta `$FN_REGISTRY_ROOT/registry.db`
+12
View File
@@ -0,0 +1,12 @@
.venv/
.mcp.json
.jupyter-port
.jupyter/
.jupyter_ystore.db
.ipython/
__pycache__/
*.pyc
.ipynb_checkpoints/
bin/
data/
.DS_Store
+1
View File
@@ -0,0 +1 @@
3.13
View File
+22
View File
@@ -0,0 +1,22 @@
---
name: turismo_spain
lang: py
domain: datascience
description: "Analisis de turismo extranjero en ciudades de Espana con datos INE FRONTUR/EGATUR y Turespaña."
tags: [turismo, spain, INE, FRONTUR, EGATUR]
uses_functions: []
uses_types: []
framework: "jupyterlab"
entry_point: "notebooks/01_turismo_extranjero.ipynb"
dir_path: "projects/app_turismo/analysis/turismo_spain"
repo_url: ""
---
## Notas
Datos fuente:
- INE tabla 2078: viajeros por puntos turisticos (138 ciudades, 2005-2026)
- INE tabla 2074: viajeros por provincia
- Dataset FRONTUR/EGATUR/Turespaña 2025: 16 CSVs con paises emisores, gasto, satisfaccion, motivaciones
Vault asociado: `turismo_spain` en `/home/lucas/vaults/turismo_spain`
+6
View File
@@ -0,0 +1,6 @@
def main():
print("Hello from turismo-spain!")
if __name__ == "__main__":
main()
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+533
View File
@@ -0,0 +1,533 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Turismo Internacional en Espana: Analisis de Mercado 2025\n",
"\n",
"**Documento de soporte para la propuesta de app de turismo**\n",
"\n",
"Fuentes: INE (FRONTUR/EGATUR), Ministerio de Industria y Turismo, Turespaña, Skyscanner, Dataestur\n",
"\n",
"---"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"tags": [
"hide-input"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Datos cargados correctamente\n"
]
}
],
"source": [
"import pandas as pd\n",
"import matplotlib\n",
"matplotlib.use('Agg')\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.ticker as mticker\n",
"from pathlib import Path\n",
"import os\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"plt.rcParams.update({\n",
" 'figure.figsize': (10, 5),\n",
" 'figure.dpi': 150,\n",
" 'font.size': 11,\n",
" 'axes.titlesize': 14,\n",
" 'axes.titleweight': 'bold',\n",
" 'axes.grid': True,\n",
" 'grid.alpha': 0.3,\n",
" 'figure.facecolor': 'white',\n",
"})\n",
"\n",
"FN_ROOT = Path(os.environ.get('FN_REGISTRY_ROOT', '')).resolve() if os.environ.get('FN_REGISTRY_ROOT') else Path.home() / 'fn_registry'\n",
"VAULT = FN_ROOT / 'vaults' / 'turismo_spain'\n",
"RAW = VAULT / 'raw'\n",
"FUENTES = RAW / 'turismo-espana-2025' / 'fuentes'\n",
"\n",
"COLORS = ['#2563EB', '#DC2626', '#059669', '#D97706', '#7C3AED', '#DB2777', '#0891B2', '#65A30D', '#EA580C', '#4F46E5']\n",
"print('Datos cargados correctamente')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Record historico: 96.8 millones de turistas\n",
"\n",
"Espana alcanzo en 2025 un maximo historico de **96.8 millones de turistas internacionales**, superando en un 15.9% los niveles pre-pandemia de 2019 (83.5M). El gasto asociado llego a **134.710 millones de euros** (+6.8% interanual).\n",
"\n",
"Este crecimiento sostenido confirma que Espana se consolida como segundo destino mundial, solo por detras de Francia."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"serie = pd.read_csv(FUENTES / '06_FRONTUR_serie_historica_anual.csv')\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 5))\n",
"colors = ['#2563EB' if a != 2025 else '#DC2626' for a in serie['anio']]\n",
"bars = ax.bar(serie['anio'].astype(str), serie['turistas_millones'], color=colors, edgecolor='white', linewidth=0.5)\n",
"\n",
"for bar, val in zip(bars, serie['turistas_millones']):\n",
" ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, f'{val}M',\n",
" ha='center', va='bottom', fontweight='bold', fontsize=11)\n",
"\n",
"ax.set_title('Turistas internacionales en Espana (millones)', pad=15)\n",
"ax.set_ylabel('Millones')\n",
"ax.set_ylim(0, 115)\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"# Annotate COVID\n",
"ax.annotate('COVID-19', xy=('2020', 18.9), xytext=('2020', 40),\n",
" arrowprops=dict(arrowstyle='->', color='gray'), ha='center', color='gray', fontsize=10)\n",
"ax.annotate('RECORD', xy=('2025', 96.8), xytext=('2025', 108),\n",
" ha='center', color='#DC2626', fontweight='bold', fontsize=11)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Mercados emisores: de donde vienen los turistas\n",
"\n",
"Los cinco principales mercados emisores concentran el **56.6% del total**. Reino Unido lidera con 19.1M, seguido de Francia (12.8M) y Alemania (12.0M).\n",
"\n",
"**Dato clave para la app:** estos cinco paises (UK, FR, DE, IT, Nordicos) son exactamente los idiomas que debe soportar el MVP. Con ingles, frances, aleman, italiano y neerlandes se cubre al **62% de los turistas**."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"paises = pd.read_csv(FUENTES / '01_FRONTUR_2025_anual_paises.csv')\n",
"p = paises[paises['pais'] != 'TOTAL'].sort_values('turistas_millones', ascending=True)\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 6))\n",
"c = ['#DC2626' if v > 3 else '#2563EB' if v > 0 else '#9CA3AF' for v in p['variacion_anual_pct']]\n",
"bars = ax.barh(p['pais'], p['turistas_millones'], color=c, edgecolor='white', linewidth=0.5)\n",
"\n",
"for bar, (_, row) in zip(bars, p.iterrows()):\n",
" signo = '+' if row['variacion_anual_pct'] > 0 else ''\n",
" ax.text(bar.get_width() + 0.2, bar.get_y() + bar.get_height()/2,\n",
" f\"{row['turistas_millones']}M ({signo}{row['variacion_anual_pct']}%)\",\n",
" va='center', fontsize=9)\n",
"\n",
"ax.set_title('Turistas por pais de origen — 2025', pad=15)\n",
"ax.set_xlabel('Millones de turistas')\n",
"ax.set_xlim(0, 24)\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"from matplotlib.patches import Patch\n",
"ax.legend([Patch(fc='#DC2626'), Patch(fc='#2563EB'), Patch(fc='#9CA3AF')],\n",
" ['Crece > 3%', 'Crece 0-3%', 'Decrece'], loc='lower right', framealpha=0.9)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Estacionalidad: oportunidad de desestacionalizacion\n",
"\n",
"La distribucion mensual muestra una concentracion extrema en verano (julio-agosto: 22.3M, el 23% del total). Los meses de invierno (noviembre-febrero) apenas suman 20.7M.\n",
"\n",
"**Oportunidad para la app:** las comunidades autonomas necesitan desestacionalizar. Una app que recomiende destinos de interior, rutas gastro y experiencias culturales fuera de temporada alta es un aliado natural para las oficinas de turismo regionales — y un canal de monetizacion B2G."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Concentracion jul-ago: 22.3M = 23% del total anual\n"
]
}
],
"source": [
"mensual = pd.read_csv(FUENTES / '04_FRONTUR_2025_mensual.csv')\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
"\n",
"# Barras mensuales\n",
"c = ['#DC2626' if v > 10 else '#D97706' if v > 8 else '#2563EB' for v in mensual['turistas_millones']]\n",
"ax1.bar(mensual['mes'].str[:3], mensual['turistas_millones'], color=c, edgecolor='white')\n",
"ax1.set_title('Llegadas mensuales 2025 (M)', pad=10)\n",
"ax1.set_ylabel('Millones')\n",
"ax1.tick_params(axis='x', rotation=45)\n",
"ax1.spines['top'].set_visible(False)\n",
"ax1.spines['right'].set_visible(False)\n",
"\n",
"# Variacion interanual\n",
"c2 = ['#059669' if v > 3 else '#2563EB' if v > 0 else '#DC2626' for v in mensual['variacion_anual_pct']]\n",
"ax2.bar(mensual['mes'].str[:3], mensual['variacion_anual_pct'], color=c2, edgecolor='white')\n",
"ax2.set_title('Variacion interanual mensual (%)', pad=10)\n",
"ax2.set_ylabel('%')\n",
"ax2.axhline(y=0, color='gray', linewidth=0.5)\n",
"ax2.tick_params(axis='x', rotation=45)\n",
"ax2.spines['top'].set_visible(False)\n",
"ax2.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(f'Concentracion jul-ago: {mensual[mensual[\"mes\"].isin([\"Julio\",\"Agosto\"])][\"turistas_millones\"].sum():.1f}M = '\n",
" f'{mensual[mensual[\"mes\"].isin([\"Julio\",\"Agosto\"])][\"turistas_millones\"].sum()/96.8*100:.0f}% del total anual')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Destinos: sobreturismo en la costa, oportunidad en el interior\n",
"\n",
"Cuatro CCAA costeras concentran el **65.2% de todos los turistas**: Cataluna (20.1M), Baleares (15.7M), Canarias (15.7M) y Andalucia (14.5M).\n",
"\n",
"Pero el dato revelador es el **crecimiento desigual**: Andalucia crece al +6.6% mientras Cataluna apenas al +0.6%. El turista busca cada vez mas destinos alternativos.\n",
"\n",
"**Dato clave para la app:** el tercer pilar (descubrimiento \"fuera del radar\") responde a una tendencia real y medible. Los datos de hoteles del INE muestran que ciudades como Algeciras (+35%), Badajoz (+30%), Murcia (+24%) y Malaga (+17%) crecen muy por encima de Barcelona (+0.6%)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"ccaa = pd.read_csv(FUENTES / '03_FRONTUR_2025_destino_ccaa.csv')\n",
"cc = ccaa[ccaa['ccaa'] != 'TOTAL'].sort_values('turistas_millones', ascending=True)\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
"\n",
"# Volumen\n",
"colors_ccaa = ['#059669' if v > 3.2 else '#2563EB' for v in cc['variacion_anual_pct']]\n",
"ax1.barh(cc['ccaa'], cc['turistas_millones'], color=colors_ccaa, edgecolor='white')\n",
"for i, (_, row) in enumerate(cc.iterrows()):\n",
" ax1.text(row['turistas_millones'] + 0.3, i, f\"{row['turistas_millones']}M\", va='center', fontsize=9)\n",
"ax1.set_title('Turistas por CCAA destino (M)', pad=10)\n",
"ax1.set_xlim(0, 25)\n",
"ax1.spines['top'].set_visible(False)\n",
"ax1.spines['right'].set_visible(False)\n",
"\n",
"# Crecimiento\n",
"cc_sort = cc.sort_values('variacion_anual_pct', ascending=True)\n",
"c_crec = ['#059669' if v > 3.2 else '#D97706' if v > 0 else '#DC2626' for v in cc_sort['variacion_anual_pct']]\n",
"ax2.barh(cc_sort['ccaa'], cc_sort['variacion_anual_pct'], color=c_crec, edgecolor='white')\n",
"ax2.axvline(x=3.2, color='gray', linestyle='--', linewidth=0.8, label='Media nacional (3.2%)')\n",
"for i, (_, row) in enumerate(cc_sort.iterrows()):\n",
" ax2.text(row['variacion_anual_pct'] + 0.2, i, f\"+{row['variacion_anual_pct']}%\", va='center', fontsize=9)\n",
"ax2.set_title('Crecimiento interanual (%)', pad=10)\n",
"ax2.legend(fontsize=8)\n",
"ax2.spines['top'].set_visible(False)\n",
"ax2.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Gasto turistico: quien gasta mas\n",
"\n",
"El gasto medio por turista varia enormemente segun el pais de origen. Los turistas estadounidenses gastan **1.511 EUR** de media, un 72% mas que los franceses (907 EUR).\n",
"\n",
"**Dato clave para la app:** priorizar la experiencia en ingles no solo cubre al mayor mercado (UK 19.1M) sino al de mayor gasto medio (USA 1.511 EUR/turista). El ingles cubre los dos segmentos de mayor valor."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"gasto = pd.read_csv(FUENTES / '05_EGATUR_2025_gasto_paises.csv')\n",
"g = gasto[~gasto['pais'].isin(['TOTAL', 'Resto'])].dropna(subset=['gasto_medio_por_turista_eur_aprox'])\n",
"g = g.sort_values('gasto_medio_por_turista_eur_aprox', ascending=True)\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 6))\n",
"c = ['#DC2626' if v > 1200 else '#D97706' if v > 900 else '#2563EB' for v in g['gasto_medio_por_turista_eur_aprox']]\n",
"bars = ax.barh(g['pais'], g['gasto_medio_por_turista_eur_aprox'], color=c, edgecolor='white')\n",
"\n",
"for bar, (_, row) in zip(bars, g.iterrows()):\n",
" ax.text(bar.get_width() + 20, bar.get_y() + bar.get_height()/2,\n",
" f\"{int(row['gasto_medio_por_turista_eur_aprox'])} EUR | Total: {int(row['gasto_total_millones_eur']):,}M\",\n",
" va='center', fontsize=9)\n",
"\n",
"ax.set_title('Gasto medio por turista segun pais de origen — 2025', pad=15)\n",
"ax.set_xlabel('EUR / turista')\n",
"ax.set_xlim(0, 1900)\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. Alojamiento: la necesidad del offline-first\n",
"\n",
"El 65% de los turistas se aloja en hoteles, pero el segmento de **vivienda de alquiler crece al +5.3%** y la **vivienda propia al +8.5%**. Estos turistas estan fuera del circuito hotelero — no tienen un recepcionista que les recomiende que hacer.\n",
"\n",
"Ademas, el 16.5% se aloja con familiares o en vivienda propia, muchas veces en **zonas rurales o costeras con mala cobertura movil**.\n",
"\n",
"**Dato clave para la app:** el pilar offline-first no es un nice-to-have, es una necesidad real para ~16M turistas anuales que estan en zonas sin cobertura fiable."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Turistas FUERA de hoteles: 33,631,848 (34.8%) — publico objetivo para offline-first\n"
]
}
],
"source": [
"aloj = pd.read_csv(FUENTES / '07_FRONTUR_2025_alojamiento.csv')\n",
"a = aloj[aloj['tipo_alojamiento'].str.contains(' - ') & ~aloj['tipo_alojamiento'].str.contains('Total')].copy()\n",
"a['tipo'] = a['tipo_alojamiento'].str.split(' - ').str[-1]\n",
"a['pct'] = a['turistas_acum2025'] / 96770515 * 100\n",
"a = a.sort_values('turistas_acum2025', ascending=False)\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
"\n",
"# Pie\n",
"wedges, texts, autotexts = ax1.pie(a['turistas_acum2025'], labels=a['tipo'],\n",
" autopct='%1.1f%%', colors=COLORS[:len(a)], startangle=90, textprops={'fontsize': 9})\n",
"ax1.set_title('Tipo de alojamiento 2025', pad=15)\n",
"\n",
"# Crecimiento\n",
"a_sort = a.sort_values('variacion_anual_pct', ascending=True)\n",
"c = ['#059669' if v > 3 else '#2563EB' for v in a_sort['variacion_anual_pct']]\n",
"ax2.barh(a_sort['tipo'], a_sort['variacion_anual_pct'], color=c, edgecolor='white')\n",
"for i, (_, row) in enumerate(a_sort.iterrows()):\n",
" ax2.text(row['variacion_anual_pct'] + 0.2, i, f\"+{row['variacion_anual_pct']}%\", va='center', fontsize=9)\n",
"ax2.set_title('Crecimiento interanual por tipo (%)', pad=10)\n",
"ax2.spines['top'].set_visible(False)\n",
"ax2.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"no_hotel = a[a['tipo'] != 'Hotelero']['turistas_acum2025'].sum()\n",
"print(f'Turistas FUERA de hoteles: {no_hotel:,.0f} ({no_hotel/96770515*100:.1f}%) — publico objetivo para offline-first')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. Satisfaccion y motivaciones: por que eligen Espana\n",
"\n",
"El **97% de los turistas se declaran satisfechos** y el 69% tiene intencion de volver en 12 meses. Los turistas latinoamericanos son los mas satisfechos (Mexico 79.5%, Brasil 79.4% de \"muy satisfechos\").\n",
"\n",
"Las motivaciones principales son **clima (26%), cultura (15.7%) y visita a familiares (12.6%)**. Pero lo mas relevante es la evolucion: los motivos culturales han crecido **+32% vs 2019** y los gastronomicos **+28%**.\n",
"\n",
"**Dato clave para la app:** el turista de 2025 ya no busca solo sol y playa. Busca experiencias autenticas, gastronomia local y cultura. Exactamente lo que una app con datos curados de pueblos, mercados, fiestas y chiringuitos puede ofrecer — y lo que TripAdvisor/Wanderlog no cubren."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"satis = pd.read_csv(FUENTES / '09_satisfaccion_por_pais.csv')\n",
"s = satis[satis['pct_muy_satisfechos'].notna() & ~satis['pais_origen'].str.contains('intencion')]\n",
"s = s.sort_values('pct_muy_satisfechos', ascending=True)\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
"\n",
"# Satisfaccion por pais\n",
"ax1.barh(s['pais_origen'], s['pct_muy_satisfechos'], color='#059669', edgecolor='white')\n",
"for i, (_, row) in enumerate(s.iterrows()):\n",
" ax1.text(row['pct_muy_satisfechos'] + 0.5, i, f\"{row['pct_muy_satisfechos']}%\", va='center', fontsize=9)\n",
"ax1.set_title('% Muy satisfechos por pais', pad=10)\n",
"ax1.set_xlim(65, 85)\n",
"ax1.spines['top'].set_visible(False)\n",
"ax1.spines['right'].set_visible(False)\n",
"\n",
"# Evolucion motivaciones\n",
"motiv_data = {'Cultural': 32, 'Gastronomico': 28, 'Media general': 10}\n",
"ax2.barh(list(motiv_data.keys()), list(motiv_data.values()),\n",
" color=['#7C3AED', '#D97706', '#9CA3AF'], edgecolor='white')\n",
"for i, (k, v) in enumerate(motiv_data.items()):\n",
" ax2.text(v + 0.5, i, f'+{v}%', va='center', fontweight='bold', fontsize=11)\n",
"ax2.set_title('Crecimiento motivaciones vs 2019 (%)', pad=10)\n",
"ax2.spines['top'].set_visible(False)\n",
"ax2.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 8. Ciudades emergentes: la dispersion del turismo\n",
"\n",
"Los datos del INE (Encuesta de Ocupacion Hotelera) por municipio revelan que las ciudades con mayor crecimiento de turismo extranjero en 2025 **no son los destinos clasicos**, sino ciudades de segundo nivel:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_csv(RAW / 'ine_2078_viajeros_puntos_turisticos.csv', sep=';', encoding='utf-8-sig')\n",
"df[['cod', 'ciudad']] = df['Puntos turísticos'].str.extract(r'^(\\d+)\\s+(.*)')\n",
"df['anio'] = df['Periodo'].str[:4].astype(int)\n",
"df['valor'] = pd.to_numeric(df['Total'].str.replace('.', '', regex=False), errors='coerce')\n",
"\n",
"ext = df[(df['Residencia'] == 'Residentes en el Extranjero') & (df['Viajeros y pernoctaciones'] == 'Viajero')]\n",
"anual = ext.groupby(['ciudad', 'anio'])['valor'].sum().reset_index()\n",
"piv = anual.pivot(index='ciudad', columns='anio', values='valor')\n",
"\n",
"crec = pd.DataFrame({'y2024': piv[2024], 'y2025': piv[2025]}).dropna()\n",
"crec['pct'] = ((crec['y2025'] / crec['y2024']) - 1) * 100\n",
"crec = crec[crec['y2024'] > 10000].sort_values('pct', ascending=True)\n",
"\n",
"top15 = crec.tail(15)\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 6))\n",
"c = ['#DC2626' if v > 20 else '#D97706' if v > 10 else '#2563EB' for v in top15['pct']]\n",
"ax.barh(top15.index, top15['pct'], color=c, edgecolor='white')\n",
"\n",
"for i, (ciudad, row) in enumerate(top15.iterrows()):\n",
" ax.text(row['pct'] + 0.5, i, f\"+{row['pct']:.0f}% ({row['y2025']:,.0f})\", va='center', fontsize=9)\n",
"\n",
"ax.set_title('Top 15 ciudades con mayor crecimiento de turismo extranjero (2024→2025)', pad=15)\n",
"ax.set_xlabel('Crecimiento %')\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 9. Imagen de Espana: los atributos que el turista valora\n",
"\n",
"Segun los estudios de mercado de Turespaña, la imagen de Espana entre los turistas se define por estos atributos:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"imagen = pd.read_csv(FUENTES / '13_imagen_espana_mercados.csv')\n",
"\n",
"rel_map = {'alta': 3, 'alta y creciente': 3.5, 'creciente': 2, 'emergente': 1}\n",
"imagen['score'] = imagen['relevancia'].map(rel_map).fillna(2)\n",
"imagen = imagen.sort_values('score', ascending=True)\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 5))\n",
"c = ['#059669' if 'creciente' in str(r) else '#2563EB' if r == 'alta' else '#D97706' for r in imagen['relevancia']]\n",
"ax.barh(imagen['atributo'], imagen['score'], color=c, edgecolor='white')\n",
"\n",
"for i, (_, row) in enumerate(imagen.iterrows()):\n",
" ax.text(row['score'] + 0.05, i, f\"[{row['relevancia']}]\", va='center', fontsize=9, style='italic')\n",
"\n",
"ax.set_title('Imagen de Espana en mercados emisores (Turespaña 2024-25)', pad=15)\n",
"ax.set_xlim(0, 4.5)\n",
"ax.set_xticks([])\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"ax.spines['bottom'].set_visible(False)\n",
"\n",
"from matplotlib.patches import Patch\n",
"ax.legend([Patch(fc='#059669'), Patch(fc='#2563EB'), Patch(fc='#D97706')],\n",
" ['Creciente', 'Alta (estable)', 'Emergente'], loc='lower right', framealpha=0.9)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 10. Cifras clave — Resumen ejecutivo\n",
"\n",
"| Metrica | Valor 2025 | Relevancia para la app |\n",
"|---------|-----------|------------------------|\n",
"| Turistas internacionales | **96.8M** (+3.2%) | Mercado masivo y en crecimiento |\n",
"| Gasto total | **134.710M EUR** (+6.8%) | Alta disposicion de gasto |\n",
"| Gasto medio/turista | **1.391 EUR** | Segmento de alto valor |\n",
"| Top 5 emisores (UK+FR+DE+IT+Nordicos) | **56.6% del total** | Cinco idiomas cubren la mayoria |\n",
"| USA gasto medio | **1.511 EUR/turista** | Mercado anglofono de altisimo valor |\n",
"| Turistas fuera de hotel | **33.6M** (34.7%) | Necesitan guia — no tienen recepcionista |\n",
"| Alojamiento alquiler | **+5.3% crecimiento** | Segmento en expansion sin atencion |\n",
"| Motivos culturales | **+32% vs 2019** | El turista busca experiencias, no solo playa |\n",
"| Motivos gastronomicos | **+28% vs 2019** | Gastronomia es diferenciador clave |\n",
"| Satisfaccion | **97%** satisfechos | Alta lealtad — 69% repite en 12 meses |\n",
"| Cataluna crecimiento | **+0.6%** | Saturacion de destinos clasicos |\n",
"| Andalucia crecimiento | **+6.6%** | Destinos emergentes en alza |\n",
"| Algeciras/Badajoz/Murcia | **+24-35%** | Ciudades secundarias explotan |\n",
"\n",
"---\n",
"\n",
"*Informe generado automaticamente a partir de datos oficiales INE/FRONTUR/EGATUR/Turespaña.*\n",
"\n",
"*Fecha: Abril 2026*"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.13.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+443
View File
@@ -0,0 +1,443 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Propuesta: App de Turismo Espana\n",
"\n",
"**\"Que hago hoy?\" — Asistente inteligente para turistas en Espana**\n",
"\n",
"---\n",
"\n",
"## El mercado\n",
"\n",
"Espana recibio **96.8 millones de turistas internacionales** en 2025 (record historico), que generaron **134.710 millones de euros** en gasto. Es el segundo destino mundial y el mercado crece al +3.2% anual.\n",
"\n",
"Pero hay un problema: **nadie esta sirviendo bien a estos turistas con tecnologia**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## El hueco de mercado\n",
"\n",
"La competencia se divide en dos bloques que no se hablan entre si:\n",
"\n",
"### Apps genericas globales\n",
"**Wanderlog, TripIt, Tripsy** — organizan bien pero no saben nada de Espana. No te dicen que la Playa de las Catedrales necesita reserva previa, ni que en agosto el interior de Andalucia supera los 45°C.\n",
"\n",
"### Apps locales fragmentadas \n",
"**Espana Turismo** (~2.000 POIs), **Komoot** (solo rutas), **Infomedusa** (solo medusas en 107 playas) — apps de un solo proposito que no gestionan el viaje.\n",
"\n",
"**Nadie esta haciendo una app nativa, rapida, con offline real, centrada exclusivamente en Espana, que combine asistencia durante el viaje con descubrimiento local autentico.**"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Setup OK\n"
]
}
],
"source": [
"import pandas as pd\n",
"import matplotlib\n",
"matplotlib.use('Agg')\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.patches as mpatches\n",
"import numpy as np\n",
"from pathlib import Path\n",
"import os\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"plt.rcParams.update({\n",
" 'figure.figsize': (10, 5), 'figure.dpi': 150, 'font.size': 11,\n",
" 'axes.titlesize': 14, 'axes.titleweight': 'bold', 'axes.grid': True,\n",
" 'grid.alpha': 0.3, 'figure.facecolor': 'white',\n",
"})\n",
"\n",
"FN_ROOT = Path(os.environ.get('FN_REGISTRY_ROOT', '')).resolve() if os.environ.get('FN_REGISTRY_ROOT') else Path.home() / 'fn_registry'\n",
"VAULT = FN_ROOT / 'vaults' / 'turismo_spain'\n",
"RAW = VAULT / 'raw'\n",
"FUENTES = RAW / 'turismo-espana-2025' / 'fuentes'\n",
"print('Setup OK')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pilar 1: Asistente diario contextual\n",
"\n",
"> *\"Hoy hace 38°C en Sevilla, evita pasear hasta las 19h. A 25 min tienes Italica con sombra y poca gente. O baja a la playa de Bolonia (1h 45min), bandera verde hoy.\"*\n",
"\n",
"El asistente combina GPS + clima + hora + preferencias del usuario para generar planes personalizados cada manana.\n",
"\n",
"### Por que funciona — datos que lo respaldan\n",
"\n",
"El **97% de los turistas se declaran satisfechos** con Espana, y el **69% tiene intencion de volver**. Pero la motivacion esta cambiando: los motivos culturales crecieron +32% y los gastronomicos +28% desde 2019. El turista ya no quiere un listado de TripAdvisor — quiere contexto."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=(10, 4.5))\n",
"\n",
"motivos = {\n",
" 'Clima': 26.0,\n",
" 'Cultura': 15.7,\n",
" 'Familiares/amigos': 12.6,\n",
" 'Precio vuelos': 56.0,\n",
" 'Precio alojamiento': 55.0,\n",
" 'Gastronomia': 31.0,\n",
"}\n",
"\n",
"cats = list(motivos.keys())\n",
"vals = list(motivos.values())\n",
"colors = ['#2563EB', '#7C3AED', '#9CA3AF', '#D97706', '#D97706', '#059669']\n",
"\n",
"bars = ax.barh(cats, vals, color=colors, edgecolor='white')\n",
"for bar, v in zip(bars, vals):\n",
" ax.text(bar.get_width() + 1, bar.get_y() + bar.get_height()/2, f'{v}%', va='center', fontweight='bold')\n",
"\n",
"ax.set_title('Motivaciones del turista para elegir Espana', pad=15)\n",
"ax.set_xlabel('% de turistas')\n",
"ax.set_xlim(0, 70)\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"# Annotation\n",
"ax.annotate('Cultura +32% vs 2019\\nGastronomia +28% vs 2019',\n",
" xy=(40, 3.5), fontsize=10, color='#7C3AED',\n",
" bbox=dict(boxstyle='round,pad=0.5', facecolor='#F3E8FF', edgecolor='#7C3AED', alpha=0.8))\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pilar 2: Descubrimiento \"fuera del radar\"\n",
"\n",
"Espana tiene un problema de **sobreturismo concentrado**: Cataluna (20.1M turistas, +0.6%) y las islas estan saturadas. Pero al mismo tiempo, **ciudades y zonas espectaculares crecen a ritmos de +20-35%** — Algeciras, Badajoz, Murcia, Merida, Malaga.\n",
"\n",
"La app puede ser el **puente** entre la saturacion costera y el interior desconocido: pueblos, calas, rutas, mercados locales, fiestas populares, bodegas."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# Contraste: destinos saturados vs emergentes\n",
"data = {\n",
" 'Destino': ['Barcelona', 'Palma', 'Lloret de Mar', 'Salou',\n",
" 'Algeciras', 'Badajoz', 'Murcia', 'Merida', 'Malaga', 'Pontevedra', 'A Coruna', 'Cangas de Onis'],\n",
" 'Crecimiento': [0.6, 2.6, 1.5, 1.0,\n",
" 34.6, 30.1, 24.0, 24.7, 17.1, 19.2, 18.5, 18.1],\n",
" 'Tipo': ['Saturado']*4 + ['Emergente']*8\n",
"}\n",
"df = pd.DataFrame(data).sort_values('Crecimiento', ascending=True)\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 6))\n",
"colors = ['#DC2626' if t == 'Saturado' else '#059669' for t in df['Tipo']]\n",
"ax.barh(df['Destino'], df['Crecimiento'], color=colors, edgecolor='white')\n",
"\n",
"for i, (_, row) in enumerate(df.iterrows()):\n",
" ax.text(row['Crecimiento'] + 0.5, i, f\"+{row['Crecimiento']}%\", va='center', fontweight='bold', fontsize=9)\n",
"\n",
"ax.set_title('Destinos saturados vs emergentes — Crecimiento turismo extranjero 2024→2025', pad=15)\n",
"ax.set_xlabel('Crecimiento interanual %')\n",
"ax.axvline(x=3.2, color='gray', linestyle='--', linewidth=0.8)\n",
"ax.text(3.5, 11.5, 'Media\\nnacional', fontsize=8, color='gray')\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"ax.legend([mpatches.Patch(fc='#DC2626'), mpatches.Patch(fc='#059669')],\n",
" ['Destinos clasicos (saturados)', 'Destinos emergentes'], loc='lower right')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Pilar 3: Offline-first con datos pre-descargados\n",
"\n",
"El turista descarga la zona donde va a estar y tiene **mapas, POIs, horarios, telefonos de emergencia y frases** sin necesitar datos moviles.\n",
"\n",
"Esto es critico: **33.6 millones de turistas** (34.7%) se alojan fuera de hoteles — en alquiler vacacional, vivienda propia o con familiares. Muchos estan en **zonas rurales o costeras con mala cobertura**. Y el segmento crece: vivienda propia +8.5%, alquiler +5.3%."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"aloj = pd.read_csv(FUENTES / '07_FRONTUR_2025_alojamiento.csv')\n",
"a = aloj[aloj['tipo_alojamiento'].str.contains(' - ') & ~aloj['tipo_alojamiento'].str.contains('Total')].copy()\n",
"a['tipo'] = a['tipo_alojamiento'].str.split(' - ').str[-1]\n",
"\n",
"# Dividir en \"con recepcionista\" vs \"sin recepcionista\"\n",
"a['segmento'] = a['tipo'].apply(lambda x: 'Con asistencia (hotel)' if x == 'Hotelero' else 'Sin asistencia — necesita la app')\n",
"seg = a.groupby('segmento')['turistas_acum2025'].sum().sort_values(ascending=True)\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5))\n",
"\n",
"# Pie segmento\n",
"ax1.pie(seg, labels=seg.index, autopct='%1.1f%%', startangle=90,\n",
" colors=['#DC2626', '#2563EB'], textprops={'fontsize': 10})\n",
"ax1.set_title('Turistas segun tipo de asistencia', pad=15)\n",
"\n",
"# Crecimiento por tipo\n",
"a_sort = a.sort_values('variacion_anual_pct', ascending=True)\n",
"c = ['#059669' if v > 3 else '#2563EB' for v in a_sort['variacion_anual_pct']]\n",
"ax2.barh(a_sort['tipo'], a_sort['variacion_anual_pct'], color=c, edgecolor='white')\n",
"for i, (_, row) in enumerate(a_sort.iterrows()):\n",
" ax2.text(row['variacion_anual_pct'] + 0.2, i, f\"+{row['variacion_anual_pct']}%\", va='center', fontsize=10, fontweight='bold')\n",
"ax2.set_title('Crecimiento por tipo de alojamiento', pad=10)\n",
"ax2.spines['top'].set_visible(False)\n",
"ax2.spines['right'].set_visible(False)\n",
"ax2.annotate('Segmentos que\\nmas necesitan la app', xy=(7, 3.5), fontsize=9, color='#059669',\n",
" bbox=dict(boxstyle='round', facecolor='#ECFDF5', edgecolor='#059669'))\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Publico objetivo: quien usa la app\n",
"\n",
"Los datos de FRONTUR/EGATUR definen el target con precision:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cobertura del MVP con 5 idiomas:\n",
" Ingles (UK+USA+IE+Nordicos): 31.8M = 33%\n",
" Frances (FR+BE): 15.9M = 16%\n",
" Aleman (DE+CH): 14.3M = 15%\n",
" Italiano: 5.7M = 6%\n",
" Neerlandes (NL): 5.0M = 5%\n",
" TOTAL: 72.7M = 75% del mercado\n"
]
}
],
"source": [
"paises = pd.read_csv(FUENTES / '01_FRONTUR_2025_anual_paises.csv')\n",
"gasto = pd.read_csv(FUENTES / '05_EGATUR_2025_gasto_paises.csv')\n",
"\n",
"# Merge paises con gasto\n",
"p = paises[paises['pais'] != 'TOTAL'].copy()\n",
"g = gasto[~gasto['pais'].isin(['TOTAL', 'Resto'])].copy()\n",
"merged = p.merge(g[['pais', 'gasto_medio_por_turista_eur_aprox']], on='pais', how='left').dropna()\n",
"\n",
"# Idioma por pais\n",
"idioma_map = {\n",
" 'Reino Unido': 'Ingles', 'Francia': 'Frances', 'Alemania': 'Aleman',\n",
" 'Italia': 'Italiano', 'Paises Bajos': 'Neerlandes', 'Belgica': 'Frances/Neerlandes',\n",
" 'Estados Unidos': 'Ingles', 'Irlanda': 'Ingles', 'Portugal': 'Portugues',\n",
" 'Suiza': 'Aleman/Frances', 'Paises Nordicos': 'Ingles (comun)',\n",
"}\n",
"merged['idioma'] = merged['pais'].map(idioma_map).fillna('Otro')\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 6))\n",
"\n",
"idioma_colors = {\n",
" 'Ingles': '#DC2626', 'Frances': '#2563EB', 'Aleman': '#D97706',\n",
" 'Italiano': '#059669', 'Neerlandes': '#7C3AED', 'Portugues': '#0891B2',\n",
" 'Frances/Neerlandes': '#4F46E5', 'Aleman/Frances': '#EA580C', 'Ingles (comun)': '#F87171',\n",
"}\n",
"c = [idioma_colors.get(i, '#9CA3AF') for i in merged['idioma']]\n",
"\n",
"scatter = ax.scatter(merged['turistas_millones'], merged['gasto_medio_por_turista_eur_aprox'],\n",
" s=merged['cuota_pct'] * 30, c=c, alpha=0.8, edgecolors='white', linewidth=1)\n",
"\n",
"for _, row in merged.iterrows():\n",
" ax.annotate(row['pais'], (row['turistas_millones'], row['gasto_medio_por_turista_eur_aprox']),\n",
" textcoords='offset points', xytext=(5, 5), fontsize=8)\n",
"\n",
"ax.set_title('Mercados emisores: volumen vs gasto por turista', pad=15)\n",
"ax.set_xlabel('Turistas (millones)')\n",
"ax.set_ylabel('Gasto medio por turista (EUR)')\n",
"ax.spines['top'].set_visible(False)\n",
"ax.spines['right'].set_visible(False)\n",
"\n",
"# Cuadrante alto valor\n",
"ax.axhline(y=1000, color='gray', linestyle=':', alpha=0.5)\n",
"ax.axvline(x=5, color='gray', linestyle=':', alpha=0.5)\n",
"ax.text(12, 1400, 'ALTO VOLUMEN\\n+ ALTO GASTO', fontsize=9, color='#DC2626', fontweight='bold',\n",
" bbox=dict(boxstyle='round', facecolor='#FEF2F2', edgecolor='#DC2626', alpha=0.8))\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Calcular cobertura por idioma\n",
"en = p[p['pais'].isin(['Reino Unido', 'Estados Unidos', 'Irlanda', 'Paises Nordicos'])]['turistas_millones'].sum()\n",
"fr = p[p['pais'].isin(['Francia', 'Belgica'])]['turistas_millones'].sum()\n",
"de = p[p['pais'].isin(['Alemania', 'Suiza'])]['turistas_millones'].sum()\n",
"it = p[p['pais'] == 'Italia']['turistas_millones'].sum()\n",
"nl = p[p['pais'] == 'Paises Bajos']['turistas_millones'].sum()\n",
"total = 96.8\n",
"\n",
"print(f'Cobertura del MVP con 5 idiomas:')\n",
"print(f' Ingles (UK+USA+IE+Nordicos): {en:.1f}M = {en/total*100:.0f}%')\n",
"print(f' Frances (FR+BE): {fr:.1f}M = {fr/total*100:.0f}%')\n",
"print(f' Aleman (DE+CH): {de:.1f}M = {de/total*100:.0f}%')\n",
"print(f' Italiano: {it:.1f}M = {it/total*100:.0f}%')\n",
"print(f' Neerlandes (NL): {nl:.1f}M = {nl/total*100:.0f}%')\n",
"print(f' TOTAL: {en+fr+de+it+nl:.1f}M = {(en+fr+de+it+nl)/total*100:.0f}% del mercado')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Diferenciadores vs competencia\n",
"\n",
"| Caracteristica | Wanderlog/TripIt | Espana Turismo | Komoot | **Esta app** |\n",
"|---------------|-----------------|----------------|--------|-------------|\n",
"| Datos curados de Espana | No | Parcial (POIs) | Solo rutas | **Si — horarios, mareas, restricciones, fiestas** |\n",
"| Asistente contextual (GPS+clima+hora) | No | No | No | **Si — plan diario personalizado** |\n",
"| Offline real | Parcial | No | Solo rutas | **Si — zona completa descargable** |\n",
"| Multiidioma (EN/FR/DE/IT/NL) | EN only | ES only | Multi | **Si — 5 idiomas = 62% del mercado** |\n",
"| Nativa (Kotlin/Swift) | React Native | React Native | Nativa | **Nativa — experiencia fluida** |\n",
"| Descubrimiento local | No | Basico | No | **Si — pueblos, mercados, fiestas, chiringuitos** |\n",
"| Gestion del viaje | Si | No | No | **Fase 2** |"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Modelo de negocio\n",
"\n",
"**App gratuita para el turista** (maxima adopcion en un mercado de 96.8M usuarios potenciales).\n",
"\n",
"Tres fuentes de ingreso:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"gasto_ccaa = pd.read_csv(FUENTES / '16_gasto_ccaa_acumulado.csv')\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5))\n",
"\n",
"# Modelo de negocio visual\n",
"canales = ['Negocios locales\\n(recomendacion destacada)', 'Oficinas turismo CCAA\\n(desestacionalizacion)', 'Premium\\n(funciones avanzadas)']\n",
"potencial = [70, 20, 10] # % del revenue mix estimado\n",
"colors = ['#2563EB', '#059669', '#D97706']\n",
"ax1.pie(potencial, labels=canales, autopct='%d%%', colors=colors, startangle=90, textprops={'fontsize': 9})\n",
"ax1.set_title('Revenue mix estimado', pad=15)\n",
"\n",
"# Gasto por CCAA\n",
"gc = gasto_ccaa.sort_values('cuota_gasto_pct', ascending=True)\n",
"ax2.barh(gc['ccaa'], gc['cuota_gasto_pct'], color='#2563EB', edgecolor='white')\n",
"for i, (_, row) in enumerate(gc.iterrows()):\n",
" ax2.text(row['cuota_gasto_pct'] + 0.3, i, f\"{row['cuota_gasto_pct']}%\", va='center', fontsize=10)\n",
"ax2.set_title('Cuota de gasto turistico por CCAA', pad=10)\n",
"ax2.set_xlabel('% del gasto total')\n",
"ax2.spines['top'].set_visible(False)\n",
"ax2.spines['right'].set_visible(False)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print('Potencial B2G: las 7 principales CCAA tienen presupuestos de turismo de 8-12 cifras.')\n",
"print('Todas buscan activamente herramientas para desestacionalizar y dispersar turistas.')\n",
"print(f'Solo el gasto de Andalucia (15.9%) supone ~21.400M EUR — el mercado es enorme.')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Roadmap MVP\n",
"\n",
"### Fase 1 — MVP (3-4 meses)\n",
"- Asistente diario contextual (GPS + clima + hora)\n",
"- Base de datos curada: 500 POIs en 3 regiones piloto (Andalucia, Cataluna, Baleares)\n",
"- Offline-first: descarga por region\n",
"- Idiomas: ingles + espanol\n",
"- Nativo iOS (Swift) + Android (Kotlin)\n",
"\n",
"### Fase 2 — Expansion (3 meses)\n",
"- 5.000+ POIs en todas las CCAA\n",
"- Frances, aleman, italiano, neerlandes\n",
"- Monetizacion: negocios locales destacados\n",
"- Integracion con oficinas de turismo regionales\n",
"\n",
"### Fase 3 — Plataforma\n",
"- Gestion del viaje (itinerarios, reservas)\n",
"- Contenido generado por locales (validado)\n",
"- API para terceros (hoteles, tour operators)\n",
"\n",
"---\n",
"\n",
"## Conclusion\n",
"\n",
"Los datos son inequivocos:\n",
"\n",
"- **96.8M turistas** que necesitan mejor tecnologia\n",
"- **33.6M fuera de hoteles** sin asistencia\n",
"- **Cultura (+32%) y gastronomia (+28%)** como motivaciones en alza\n",
"- **Destinos emergentes creciendo al +20-35%** mientras los clasicos se estancan\n",
"- **5 idiomas cubren el 62%** del mercado\n",
"- **Cero competidores** en el nicho de app nativa + offline + asistencia contextual + Espana\n",
"\n",
"El hueco existe, el mercado es masivo, y los datos lo respaldan.\n",
"\n",
"---\n",
"\n",
"*Todos los datos provienen de fuentes oficiales: INE (FRONTUR/EGATUR), Ministerio de Industria y Turismo, Turespaña, Skyscanner/Dataestur.*\n",
"\n",
"*Abril 2026*"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.13.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+19
View File
@@ -0,0 +1,19 @@
[project]
name = "turismo-spain"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"jupyter>=1.1.1",
"jupyter-collaboration>=4.3.0",
"jupyter-mcp-server>=0.4.0",
"jupyterlab>=4.5.6",
"matplotlib>=3.10.8",
"numpy>=2.4.4",
"openpyxl>=3.1.5",
"pandas>=3.0.2",
"playwright>=1.58.0",
"plotly>=6.7.0",
"requests>=2.33.1",
]
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# Jupyter Lab — modo colaborativo con autodeteccion de puerto
# Generado por write_jupyter_launcher (fn_registry)
find_free_port() {
for port in 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899; do
if ! ss -tln 2>/dev/null | grep -q ":${port} " && \
! lsof -i:"$port" >/dev/null 2>&1; then
echo $port
return
fi
done
echo 8888
}
PORT=${1:-$(find_free_port)}
cd "$(dirname "$0")"
echo $PORT > .jupyter-port
source .venv/bin/activate 2>/dev/null || true
# IPython startup: cargar .ipython/ local (FN_REGISTRY_ROOT, helpers, sys.path)
if [ -d "$(pwd)/.ipython" ]; then
export IPYTHONDIR="$(pwd)/.ipython"
fi
if ! python -c "import jupyter_collaboration" 2>/dev/null; then
echo "ERROR: jupyter-collaboration no esta instalado"
echo "Instala con: uv add jupyter-collaboration"
exit 1
fi
echo "════════════════════════════════════════════════"
echo " Jupyter Lab + Colaboracion en puerto $PORT"
echo "════════════════════════════════════════════════"
echo ""
echo " Abre: http://localhost:$PORT"
echo " Ctrl+C para detener"
echo ""
jupyter lab \
--port=$PORT \
--no-browser \
--ServerApp.token='' \
--ServerApp.password='' \
--ServerApp.disable_check_xsrf=True \
--ServerApp.allow_origin='*' \
--ServerApp.root_dir="$(pwd)" \
--collaborative
Generated
+2668
View File
File diff suppressed because it is too large Load Diff