""" VUTIA - Sistema de Análisis de Viviendas de Uso Turístico Ayuntamiento de Dénia - Plataforma de Análisis Institucional """ import streamlit as st import pandas as pd import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import numpy as np from datetime import datetime from io import BytesIO import os # Configuración de la página st.set_page_config( page_title="VUTIA - Sistema de Análisis VUT", page_icon="📊", layout="wide", initial_sidebar_state="expanded" ) # CSS Profesional Institucional (copiado del original) st.markdown(""" """, unsafe_allow_html=True) # Colores corporativos COLORS = { 'VUT_CONFIRMADA': '#006AA7', 'VUT_POSIBLE': '#017CB5', 'VUT_BAJACONFIANZA_NOREGLADA': '#009992', 'HABITUAL': '#00AA98', 'SEGUNDA_RESIDENCIA': '#43C179', 'VACIA': '#8BB640' } def get_chart_layout(): return { 'paper_bgcolor': 'rgba(20, 24, 50, 0.5)', 'plot_bgcolor': 'rgba(26, 31, 58, 0.5)', 'font': {'family': 'Inter', 'size': 12, 'color': 'white'}, 'margin': {'t': 40, 'r': 20, 'b': 40, 'l': 50}, 'xaxis': {'gridcolor': 'rgba(67, 193, 121, 0.1)', 'color': 'white'}, 'yaxis': {'gridcolor': 'rgba(67, 193, 121, 0.1)', 'color': 'white'} } @st.cache_data def cargar_datos(uploaded_file=None): """Carga datos desde archivo subido o usa datos de demostración""" if uploaded_file is not None: try: df = pd.read_excel(uploaded_file) return df except Exception as e: st.error(f"Error al cargar el archivo: {e}") return None else: # Datos de demostración st.info("📊 Usando datos de demostración. Sube tu archivo Excel para ver tus datos reales.") return crear_datos_demo() def crear_datos_demo(): """Crea datos de demostración para pruebas""" np.random.seed(42) n_samples = 1000 categorias = ['VUT_CONFIRMADA', 'VUT_POSIBLE', 'VUT_BAJACONFIANZA_NOREGLADA', 'HABITUAL', 'SEGUNDA_RESIDENCIA', 'VACIA'] barrios = ['Centro', 'Les Rotes', 'Les Marines', 'La Xara', 'Jesus Pobre', 'Els Poblets', 'Dénia Nord', 'Dénia Sud'] data = { 'Direccion': [f'Calle Demo {i}' for i in range(n_samples)], 'Barrio': np.random.choice(barrios, n_samples), 'Categoria': np.random.choice(categorias, n_samples, p=[0.15, 0.20, 0.15, 0.25, 0.15, 0.10]), 'Confianza': np.random.uniform(0.3, 1.0, n_samples), 'Consumo_Total_Periodo_m3': np.random.uniform(50, 500, n_samples), 'Ratio_Verano_Invierno': np.random.uniform(0.5, 3.0, n_samples) } return pd.DataFrame(data) # Header institucional st.markdown("""
VUTIA
Sistema de Análisis de Viviendas de Uso Turístico
VUT
Sistema de Análisis
""", unsafe_allow_html=True) # Subida de archivo with st.sidebar: st.markdown("### 📁 CARGAR DATOS") uploaded_file = st.file_uploader( "Sube tu archivo Excel", type=['xlsx', 'xls'], help="Archivo con datos de clasificación VUT" ) st.markdown("---") # Cargar datos df = cargar_datos(uploaded_file) if df is None: st.error("No se pudieron cargar los datos") st.stop() # SIDEBAR DE FILTROS with st.sidebar: st.markdown("### 🔍 FILTROS") categorias = df['Categoria'].unique().tolist() cat_sel = st.multiselect("Categorías", categorias, default=categorias) barrios = ['Todos'] + sorted(df['Barrio'].unique().tolist()) barrio_sel = st.selectbox("Barrio", barrios) confianza_min = st.slider("Confianza Mínima (%)", 0, 100, 0, 5) # Aplicar filtros df_filtrado = df.copy() if cat_sel: df_filtrado = df_filtrado[df_filtrado['Categoria'].isin(cat_sel)] if barrio_sel != 'Todos': df_filtrado = df_filtrado[df_filtrado['Barrio'] == barrio_sel] df_filtrado = df_filtrado[df_filtrado['Confianza'] >= confianza_min / 100] # Estadísticas st.markdown("### ESTADÍSTICAS") col1, col2 = st.columns(2) with col1: st.metric("Filtradas", f"{len(df_filtrado):,}") with col2: st.metric("Total", f"{len(df):,}") if len(df_filtrado) < len(df): pct = (len(df_filtrado) / len(df)) * 100 st.caption(f"Mostrando {pct:.1f}% del total") st.markdown("---") st.caption("**VUTIA v3.0**") st.caption("© 2026 Ayuntamiento de Dénia") # TABS tab1, tab2, tab3, tab4 = st.tabs([ "Panel General", "Análisis VUT", "Datos", "Exportación" ]) # TAB 1: PANEL GENERAL with tab1: st.markdown("## Panel General") st.markdown("") # Métricas principales col1, col2, col3, col4, col5, col6 = st.columns(6) with col1: n = len(df_filtrado[df_filtrado['Categoria'] == 'VUT_CONFIRMADA']) st.metric("VUT Confirmada", f"{n:,}", f"{(n/len(df_filtrado)*100):.1f}%" if len(df_filtrado) > 0 else "0%") with col2: n = len(df_filtrado[df_filtrado['Categoria'] == 'VUT_POSIBLE']) st.metric("VUT Posible", f"{n:,}", f"{(n/len(df_filtrado)*100):.1f}%" if len(df_filtrado) > 0 else "0%") with col3: n = len(df_filtrado[df_filtrado['Categoria'] == 'VUT_BAJACONFIANZA_NOREGLADA']) st.metric("VUT Baja Conf.", f"{n:,}", f"{(n/len(df_filtrado)*100):.1f}%" if len(df_filtrado) > 0 else "0%") with col4: n = len(df_filtrado[df_filtrado['Categoria'] == 'HABITUAL']) st.metric("Habitual", f"{n:,}", f"{(n/len(df_filtrado)*100):.1f}%" if len(df_filtrado) > 0 else "0%") with col5: n = len(df_filtrado[df_filtrado['Categoria'] == 'SEGUNDA_RESIDENCIA']) st.metric("Segunda Res.", f"{n:,}", f"{(n/len(df_filtrado)*100):.1f}%" if len(df_filtrado) > 0 else "0%") with col6: n = len(df_filtrado[df_filtrado['Categoria'] == 'VACIA']) st.metric("Vacía", f"{n:,}", f"{(n/len(df_filtrado)*100):.1f}%" if len(df_filtrado) > 0 else "0%") st.markdown("---") # Gráfico de distribución st.markdown("### Distribución por Categorías") dist = df_filtrado['Categoria'].value_counts() fig = go.Figure(data=[go.Pie( labels=dist.index, values=dist.values, hole=.45, marker=dict( colors=[COLORS.get(c, '#017CB5') for c in dist.index], line=dict(color='rgba(10, 14, 39, 0.8)', width=3) ), textfont=dict(size=12, family='Inter', color='white'), hovertemplate='%{label}
%{value:,} viviendas
%{percent}' )]) layout = get_chart_layout() layout['height'] = 400 fig.update_layout(layout) st.plotly_chart(fig, use_container_width=True) # TAB 2: ANÁLISIS VUT with tab2: st.markdown("## Análisis Detallado de VUT") vut_categories = ['VUT_CONFIRMADA', 'VUT_POSIBLE', 'VUT_BAJACONFIANZA_NOREGLADA'] df_vut = df_filtrado[df_filtrado['Categoria'].isin(vut_categories)] col1, col2, col3 = st.columns(3) with col1: st.metric("Total VUT", f"{len(df_vut):,}") with col2: st.metric("Confirmadas", f"{len(df_vut[df_vut['Categoria']=='VUT_CONFIRMADA']):,}") with col3: avg_conf = df_vut['Confianza'].mean() * 100 if len(df_vut) > 0 else 0 st.metric("Confianza Media", f"{avg_conf:.1f}%") st.markdown("---") # Top 20 VUT st.markdown("### Top 20 VUT por Confianza") top20 = df_vut.nlargest(20, 'Confianza') for idx, row in top20.iterrows(): with st.container(): col1, col2, col3, col4 = st.columns([3, 2, 2, 2]) with col1: st.write(f"**{row['Direccion']}**") with col2: st.write(f"Barrio: {row['Barrio']}") with col3: st.write(f"Categoría: {row['Categoria']}") with col4: st.write(f"Confianza: {row['Confianza']*100:.1f}%") st.markdown("---") # TAB 3: DATOS with tab3: st.markdown("## Datos Detallados") # Búsqueda busqueda = st.text_input("🔍 Buscar por dirección o barrio") df_mostrar = df_filtrado.copy() if busqueda: mask = (df_mostrar['Direccion'].str.contains(busqueda, case=False, na=False) | df_mostrar['Barrio'].str.contains(busqueda, case=False, na=False)) df_mostrar = df_mostrar[mask] st.dataframe( df_mostrar[['Direccion', 'Barrio', 'Categoria', 'Confianza', 'Consumo_Total_Periodo_m3']], use_container_width=True, height=600 ) st.caption(f"Mostrando {len(df_mostrar):,} de {len(df_filtrado):,} viviendas") # TAB 4: EXPORTACIÓN with tab4: st.markdown("## Exportación de Datos") st.markdown("### Descargar Datos Filtrados") # Convertir a Excel buffer = BytesIO() with pd.ExcelWriter(buffer, engine='openpyxl') as writer: df_filtrado.to_excel(writer, index=False, sheet_name='Datos') buffer.seek(0) st.download_button( label="📊 Descargar Excel", data=buffer, file_name=f"vutia_datos_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) # Convertir a CSV csv = df_filtrado.to_csv(index=False).encode('utf-8') st.download_button( label="📄 Descargar CSV", data=csv, file_name=f"vutia_datos_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", mime="text/csv" )