# Importar todas las librerias que se necesitaran para trabajar

import matplotlib.pyplot as plt
import pandas as pd
from pathlib import Path
from difflib import get_close_matches
import os
import time
import math
import plotly.express as px
from matplotlib.backends.backend_pdf import PdfPages
import numpy as np
from pymongo import MongoClient


# ---- Limpieza datos e importación de datos ---- #

def leer_archivo(ruta_archivo):
    ruta = Path(ruta_archivo)
    
    if not ruta.exists():
        raise FileNotFoundError(f"El archivo no se encontró: {ruta}")
    
    extension = ruta.suffix.lower()

    # Manejo para archivos Excel
    if extension == '.xls':
        try:
            return pd.read_excel(ruta, engine='xlrd')  # motor para archivos .xls antiguos
        except Exception as e:
            raise ValueError(f"No se pudo leer el archivo .xls: {ruta}\nError: {e}")
        
    elif extension == '.xlsx':
        try:
            return pd.read_excel(ruta)  # motor predeterminado openpyxl
        except Exception as e:
            raise ValueError(f"No se pudo leer el archivo .xlsx: {ruta}\nError: {e}")

    # Manejo para CSV con múltiples codificaciones
    elif extension == '.csv':
        encodings_a_probar = ['utf-8', 'ISO-8859-1', 'latin1', 'cp1252']
        for enc in encodings_a_probar:
            try:
                return pd.read_csv(ruta, encoding=enc)
            except UnicodeDecodeError:
                continue
            except Exception as e:
                raise ValueError(f"Error inesperado al leer CSV con codificación '{enc}': {e}")
        
        raise UnicodeDecodeError(
            f"No se pudo leer el archivo CSV con las codificaciones: {', '.join(encodings_a_probar)}"
        )
    
    else:
        raise ValueError(f"Formato de archivo no soportado: '{extension}'. Solo se permiten .csv, .xls, .xlsx")

def diagnostico_base_datos_pdf(df, nombre_pdf="diagnostico_datos.pdf"):
    with PdfPages(nombre_pdf) as pdf:
        # Página 1: Resumen general
        fig, ax = plt.subplots(figsize=(10, 6))
        texto = f"""Resumen general:
        Dimensiones: {df.shape}
        Columnas: {', '.join(df.columns[:10]) + ('...' if df.shape[1] > 10 else '')}
        Duplicados: {df.duplicated().sum()}
        Tipos de datos: {df.dtypes.value_counts().to_dict()}
        """
        ax.axis("off")
        ax.text(0.01, 0.9, texto, fontsize=12, verticalalignment='top', family='monospace')
        pdf.savefig(fig)
        plt.close()

        # Página 2: Valores nulos
        nulos = df.isnull().sum()
        nulos = nulos[nulos > 0].sort_values(ascending=False)
        if not nulos.empty:
            fig, ax = plt.subplots(figsize=(10, 6))
            nulos.plot(kind="barh", ax=ax, color="salmon")
            ax.set_title("Valores nulos por columna")
            pdf.savefig(fig)
            plt.close()

        # Página 3: Columnas irrelevantes (1 solo valor)
        unicos = df.nunique()
        irrelevantes = unicos[unicos <= 1]
        if not irrelevantes.empty:
            fig, ax = plt.subplots(figsize=(10, 6))
            irrelevantes.plot(kind="barh", ax=ax, color="gray")
            ax.set_title("Columnas con un solo valor único")
            pdf.savefig(fig)
            plt.close()

        # Página 4: Resumen numérico
        fig, ax = plt.subplots(figsize=(10, 6))
        ax.axis("off")
        resumen = df.describe().round(2).to_string()
        ax.text(0, 1, resumen, fontsize=10, verticalalignment='top', family='monospace')
        pdf.savefig(fig)
        plt.close()

        # Página 5+: Valores únicos por columnas tipo texto
        columnas_obj = df.select_dtypes(include='object').columns
        for col in columnas_obj:
            valores = df[col].value_counts(dropna=False).head(10)
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.barh(valores.index.astype(str), valores.values, color='skyblue')
            ax.set_title(f"Top 10 valores en '{col}'")
            pdf.savefig(fig)
            plt.close()

    print(f"✅ Diagnóstico guardado en: {nombre_pdf}")

def limpiar_base_vivienda(df):
    # 1. Eliminar columnas con un solo valor}
    import numpy as np
    columnas_irrelevantes = df.columns[df.nunique() <= 1]
    df = df.drop(columns=columnas_irrelevantes)

    # 2. Reemplazar "*" por NaN
    df = df.replace("*", np.nan)

    # 3. Convertir todas las columnas posibles a numérico (excepto tipo object relevantes)
    for col in df.columns:
        try:
            df[col] = pd.to_numeric(df[col], errors='ignore')
        except:
            pass

    # 4. (Opcional) Si hay columnas de coordenadas en formato texto, podrían procesarse por separado.

    # 5. (Opcional) Borrar columnas que sepas que no usarás
    # columnas_descartar = ['LONGITUD', 'LATITUD']  # si no las vas a convertir
    # df = df.drop(columns=columnas_descartar)

    return df

def analisis_base_dato_1(data):
    """
    Esta función realiza un análisis exploratorio inicial de una base de datos proporcionada.
    Proporciona información sobre el tipo de datos, valores frecuentes, valores nulos, 
    valores mínimos y máximos, importancia de las variables.
    Parámetros:
    - data (DataFrame): Un DataFrame de pandas que contiene los datos a analizar.

    Retorna:
    - result (DataFrame): Un DataFrame que resume el análisis de las variables en el conjunto de datos.
    """
    
    import pandas as pd
    import numpy as np
    # Lista de nombres de las columnas para documentar la base de datos
    columns = pd.DataFrame(list(data.columns.values), columns=['Nombres'], index=list(data.columns.values))

    # Lista de tipos de datos de cada columna
    data_types = pd.DataFrame(data.dtypes, columns=['Data_Types'])

    # Inicializa DataFrames vacíos para almacenar los valores más y menos frecuentes
    most_frequent_values = pd.DataFrame(columns=['Valor más frecuente'])
    least_frequent_values = pd.DataFrame(columns=['Valor menos frecuente'])
    
    # Inicializa DataFrame vacío para contar los valores únicos por columna
    unique_values = pd.DataFrame(columns=['Unique_Values'])
    
    # Inicializa DataFrame vacío para registrar los valores mínimos y máximos
    min_values = pd.DataFrame(columns=['Min'])
    max_values = pd.DataFrame(columns=['Max'])
    
    # Inicializa DataFrame vacío para registrar la importancia de las variables
    importance = pd.DataFrame(columns=['Importance_Percentage'])

    # Calcula el número de valores perdidos por columna
    missing_values = pd.DataFrame(data.isnull().sum(), columns=['Missing_Values'])
    
    # Calcula el número de valores presentes por columna
    present_values = pd.DataFrame(data.count(), columns=['Present_Values'])   
    
    #Itera sobre todas las columnas de la base de datos para realizar los análisis
    for col in data.columns:
        if data[col].dtype == 'object' or data[col].dtype.name == 'category':
            # Si la columna es categórica (tipo 'object' o 'category')
            value_counts = data[col].value_counts()
            # Valor más frecuente (modo)
            most_frequent_values.loc[col] = value_counts.idxmax() if not value_counts.empty else "N/A"
            # Valor menos frecuente
            least_frequent_values.loc[col] = value_counts.idxmin() if not value_counts.empty else "N/A"
            # Número de valores únicos
            unique_values.loc[col] = data[col].nunique()
            # Cálculo de la entropía como medida de importancia para las variables categóricas
            value_counts_normalized = data[col].value_counts(normalize=True)
            entropy = -np.sum(value_counts_normalized * np.log2(value_counts_normalized))
            importance.loc[col] = entropy
        else:
            # Si la columna es numérica
            min_values.loc[col] = data[col].min()  # Valor mínimo
            max_values.loc[col] = data[col].max()  # Valor máximo
            unique_values.loc[col] = data[col].nunique()  # Número de valores únicos
            # Cálculo del coeficiente de variación como medida de importancia para las variables numéricas
            cv = (data[col].std() / data[col].mean()) * 100 if data[col].std() != 0 else 0
            importance.loc[col] = cv
    # Normaliza el porcentaje de importancia para cada variable
    importance['Importance_Percentage'] = (importance['Importance_Percentage'] / importance['Importance_Percentage'].sum()) * 100

    # Une todos los DataFrames generados en uno solo
    result = columns.join(data_types).join(most_frequent_values).join(least_frequent_values).join(missing_values).join(present_values).join(unique_values).join(min_values).join(max_values).join(importance)

    # Imprime el número total de filas y columnas de la base de datos
    print(f"La base de datos tiene un total de {data.shape[0]} filas y {data.shape[1]} columnas.")


    # Retorna el DataFrame resultante con todos los análisis
    return result

def columnas_vacias(df):
    # Encontrar columnas completamente vacías (con NaN en todos sus valores)
    columnas_vacias = df.columns[df.isna().all()]
    return columnas_vacias

def analizar_porcentaje_datos_columans(df):
    """
    Analiza la cobertura de datos en cada columna de un DataFrame.
    
    La función evalúa el porcentaje de valores no nulos en cada columna y clasifica las columnas 
    en diferentes niveles de cobertura en intervalos de 10%, desde 100% hasta 0%.
    
    Parámetros:
    df (pd.DataFrame): DataFrame a analizar.
    
    Retorna:
    dict: Un diccionario donde las claves son descripciones del porcentaje de datos presentes 
          y los valores son listas de nombres de columnas que cumplen con dicho criterio.

    Como llamar a la funcion:
    resultado = analizar_columnas_vacias(base_datos)
    for key, value in resultado.items():
        print(f"{key}: {value}")

    """
    import pandas as pd
    info_columnas = {}
    total_filas = len(df)
    cobertura = {col: df[col].count() / total_filas * 100 for col in df.columns}  # Calcula porcentaje de datos
    
    columnas_asignadas = set()
    
    for porcentaje in range(100, -10, -10):  # Incluye 0% de datos
        columnas_filtradas = [col for col, pct in cobertura.items() if pct >= porcentaje and col not in columnas_asignadas]
        info_columnas[f"Columnas con al menos {porcentaje}% de datos"] = columnas_filtradas
        columnas_asignadas.update(columnas_filtradas)
    
    return info_columnas

# Estadísticas de una columna numérica
def estadistica_descriptiva(df, columna):
    """
    Función que evalúa las métricas estadísticas de una columna numérica en un DataFrame.
    
    La función calcula varias métricas estadísticas, como el valor máximo, mínimo, promedio,
    mediana, desviación estándar, percentiles, asimetría, curtosis, y el rango intercuartílico
    (IQR) de la columna proporcionada. Luego, crea un DataFrame con los resultados, lo guarda en
    un archivo CSV y muestra el resumen de las métricas.

    Argumentos:
    df (DataFrame): El DataFrame que contiene la columna a evaluar.
    columna (str): El nombre de la columna a evaluar.

    Salida:
    - Un DataFrame con las métricas estadísticas.
    - Un archivo CSV con el resumen estadístico.
    - Un texto explicando las conclusiones basadas en las métricas calculadas.
    """

    import pandas as pd

    # Verificar que la columna proporcionada sea numérica
    if not pd.api.types.is_numeric_dtype(df[columna]):
        raise ValueError(f"La columna '{columna}' no es numérica. Por favor, ingrese una columna numérica.")
    
    # Calcular las métricas
    valor_mas_alto = df[columna].max()
    valor_mas_bajo = df[columna].min()
    promedio = df[columna].mean()
    mediana = df[columna].median()
    desviacion_estandar = df[columna].std()
    Q1 = df[columna].quantile(0.25)
    Q3 = df[columna].quantile(0.75)
    IQR = Q3 - Q1
    percentil_10 = df[columna].quantile(0.10)
    percentil_50 = df[columna].quantile(0.50)  # Equivalente a la mediana
    percentil_90 = df[columna].quantile(0.90)
    asimetria = df[columna].skew()
    curtosis = df[columna].kurtosis()

    # Crear un DataFrame con los resultados
    resumen_estadistico = pd.DataFrame({
        'Métrica': [
            'Máxima',
            'Mínimo',
            'Promedio',
            'Mediana',
            'Desviación Estándar',
            'Rango Intercuartílico (IQR)',
            'Percentil 10',
            'Percentil 50',
            'Percentil 90',
            'Asimetría',
            'Curtosis'
        ],
        'Valor': [
            valor_mas_alto,
            valor_mas_bajo,
            promedio,
            mediana,
            desviacion_estandar,
            IQR,
            percentil_10,
            percentil_50,
            percentil_90,
            asimetria,
            curtosis
        ]
    })

    # Guardar el DataFrame con los resultados en un archivo CSV
    resumen_estadistico.to_csv(f"Resumen_Estadistico_{columna}.csv", index=False, encoding='ISO-8859-1')

    # Generar un resumen de conclusiones basado en las métricas
    conclusiones = []

    # Conclusiones
    if asimetria > 0:
        conclusiones.append("La distribución de los datos está sesgada hacia la derecha.")
    elif asimetria < 0:
        conclusiones.append("La distribución de los datos está sesgada hacia la izquierda.")
    else:
        conclusiones.append("La distribución de los datos es simétrica.")

    if curtosis > 0:
        conclusiones.append("Los datos tienen colas más pesadas que una distribución normal (curtosis leptocúrtica).")
    elif curtosis < 0:
        conclusiones.append("Los datos tienen colas más ligeras que una distribución normal (curtosis platicúrtica).")
    else:
        conclusiones.append("La distribución de los datos tiene una curtosis normal (curtosis mesocúrtica).")

    if IQR < 10:
        conclusiones.append("Los datos tienen una dispersión baja.")
    elif IQR > 50:
        conclusiones.append("Los datos tienen una dispersión alta.")
    else:
        conclusiones.append("La dispersión de los datos es moderada.")

    # Mostrar conclusiones
    print("\nConclusiones:")
    for conclusion in conclusiones:
        print(f"- {conclusion}")

    return resumen_estadistico

# Información sobre la distribución de sus valores en cuanto a su frecuencia, porcentaje y porcentaje acumulado
def analizar_columnas(df, nombre_columna, porcentaje_acumulado):
    """
    Analiza una columna del DataFrame proporcionando información sobre la distribución de sus valores
    en cuanto a su frecuencia, porcentaje y porcentaje acumulado.

    Parámetros:
    df (DataFrame): El DataFrame que contiene los datos a analizar.
    nombre_columna (str): El nombre de la columna que se analizará.
    porcentaje_acumulado (float): El porcentaje acumulado máximo que debe mostrarse en los resultados.

    Retorna:
    DataFrame: Un DataFrame con las siguientes columnas:
        - 'nombre_columna': Los valores únicos en la columna analizada.
        - 'Cantidad': La cantidad de ocurrencias de cada valor.
        - 'Porcentaje': El porcentaje de ocurrencias de cada valor respecto al total.
        - 'Porcentaje Acumulado': El porcentaje acumulado hasta cada valor.
    
    Lanza:
    ValueError: Si 'nombre_columna' no existe en el DataFrame.
    """

    # Validación de si la columna existe en el DataFrame
    if nombre_columna not in df.columns:
        raise ValueError(f"La columna '{nombre_columna}' no existe en el DataFrame.")
    
    # Contar la frecuencia de cada valor en la columna
    df_ana = df[nombre_columna].value_counts().reset_index()
    df_ana.columns = [nombre_columna, 'Cantidad']

    # Calcular el total de elementos en la columna para determinar los porcentajes
    suma_activ = df_ana["Cantidad"].sum()
    
    # Agregar la columna de porcentaje
    df_ana["Porcentaje"] = (df_ana["Cantidad"] / suma_activ) * 100
    
    # Agregar la columna de porcentaje acumulado
    df_ana["Porcentaje Acumulado"] = df_ana["Porcentaje"].cumsum()
    
    # Filtrar solo los valores cuyo porcentaje acumulado es menor o igual al especificado
    df_ana = df_ana[df_ana["Porcentaje Acumulado"] <= porcentaje_acumulado]
    
    # Eliminar columnas no necesarias 
    df_ana = df_ana.drop(columns = ["Porcentaje","Porcentaje Acumulado"])

    # Imprimir el mensaje con el porcentaje acumulado que se está utilizando

    print(f"Estos son los datos que representan el {porcentaje_acumulado}% de los valores. De la columna {nombre_columna}")
    return df_ana 
# ---------------------------------------------- #




# ---- Obtener datos de Google Maps ---- #
# Clave de API de Google Maps
#API_KEY = 'AIzaSyD-8YtqddehkKMliUFm7hC7d_TQvaXrIHs'
# Clave Kevin
#API_KEY = 'AIzaSyDiNy34j9wI4gQ2hE1TjpbQVFcsoVNaYRo'
comercios = [ 
    "Tienda de ropa",
    "Boutique de moda",
    "Zapatería",
    "Tienda de ropa deportiva",
    "Tienda de accesorios",
    "Tienda de ropa infantil",
    "Refaccionaria",
    "Taller mecánico",
    "Venta de autopartes",
    "Lavado de autos",
    "Tienda de llantas y alineación",
    "Venta de motocicletas y accesorios",
    "Restaurante",
    "Cafetería",
    "Taquería",
    "Pizzería",
    "Pastelería y repostería",
    "Tienda de abarrotes",
    "Carnicería",
    "Frutería y verdulería",
    "Tienda de productos orgánicos",
    "Tienda de celulares y accesorios",
    "Venta y reparación de computadoras",
    "Tienda de electrodomésticos",
    "Venta de videojuegos y consolas",
    "Tienda de cámaras y fotografía",
    "Farmacia", 
    "Spa",
    "Spa y centro de masajes", 
    "Estética" "peluquería",
    "Barbería",
    "Tienda de cosméticos", 
    "Tienda de productos naturales",
    "suplementos",
    "Gym", 
    "Pilates",
    "Box",
    "Tienda de muebles",
    "Ferretería",
    "Tienda de iluminación",
    "Venta de artículos de decoración",
    "Tienda de colchones",
    "Librería",
    "Tienda de música e instrumentos",
    "Tienda de cómics y coleccionables",
    "Juguetería",
    "Cine o sala de entretenimiento",
    "Agencia de viajes",
    "Inmobiliaria",
    "Consultoría empresarial",
    "Centro de copiado e impresión",
    "Agencia de publicidad y marketing",
    "Escuela de idiomas o cursos",
    "Tienda de artículos deportivos",
    "Tienda de pesca y camping",
    "Tienda de bicicletas y accesorios",
    "Tienda de alimentos y accesorios para mascotas",
    "Veterinaria",
    "Estética canina",
    "Despacho de abogados",
    "Notaría",
    "Correduría pública",
    "Mediación y arbitraje",
    "Agencia de cobranza",
    "Oficina de gestoría legal y trámites",
    "Consultoría en propiedad intelectual",
    "Despacho contable",
    "Asesoría financiera",
    "Casa de bolsa",
    "Cooperativa de ahorro y préstamo",
    "Agencia de seguros",
    "Consultoría en inversiones",
    "Oficina de crédito y cobranza",
    "Hospital",
    "Clínica médica",
    "Consultorio dental",
    "Laboratorio clínico",
    "Centro de rehabilitación",
    "Óptica",
    "Farmacia con consultorio",
    "Nutrición y dietética",
    "Psicología y terapia",
    "Hotel de lujo",
    "Hotel boutique",
    "Hotel de negocios",
    "Motel",
    "Hostal",
    "Cabañas y ecoturismo",
    "Airbnb y alquiler vacacional",
    "Resort con todo incluido",
    "Glamping (camping de lujo)",
    "Bomberos",
    "Protección Civil",
    "Policía Municipal",
    "Policía Estatal",
    "Policía Federal (Guardia Nacional)",
    "Seguridad Privada",
    "C5 o C4 (centro de monitoreo y emergencias)",
    "Cruz Roja",
    "Cruz Ámbar",
    "ERUM",
    "Grupos de Rescate en Montaña y Mar",
    "Hospitales de Urgencias",
    "Centros de donación de sangre y trasplantes",
    "Presidencia de la República",
    "Secretaría de Hacienda y Crédito Público (SHCP)",
    "Secretaría de Educación Pública (SEP)",
    "Secretaría de Salud (SSA)",
    "Secretaría de Seguridad y Protección Ciudadana",
    "Secretaría de Relaciones Exteriores (SRE - pasaportes, consulados)",
    "Secretaría de Turismo",
    "Instituto Nacional de Migración (INM)",
    "Servicio de Administración Tributaria (SAT)",
    "Gobernatura del Estado",
    "Secretaría de Finanzas del Estado",
    "Secretaría de Movilidad y Transporte",
    "Secretaría de Desarrollo Económico",
    "Secretaría de Medio Ambiente",
    "Registro Civil (actas de nacimiento, matrimonio, defunción)",
    "Fiscalía General del Estado",
    "Presidencia Municipal",
    "Tesorería Municipal",
    "Desarrollo Urbano y Obras Públicas",
    "Seguridad Pública y Tránsito Municipal",
    "Protección Civil",
    "Servicios Públicos Municipales (agua, recolección de basura)",
    "DIF Municipal (Desarrollo Integral de la Familia)"]
categorias_establecimientos = {
    "Ropa y Moda": [
        "Tienda de ropa", "Boutique de moda", "Zapatería", "Tienda de ropa deportiva",
        "Tienda de accesorios", "Tienda de ropa infantil"
    ],
    "Automotriz y Refacciones": [
        "Refaccionaria", "Taller mecánico", "Venta de autopartes", "Lavado de autos",
        "Tienda de llantas y alineación", "Venta de motocicletas y accesorios"
    ],
    "Alimentos y Bebidas": [
        "Restaurante", "Cafetería", "Taquería", "Pizzería", "Pastelería y repostería",
        "Tienda de abarrotes", "Carnicería", "Frutería y verdulería", "Tienda de productos orgánicos"
    ],
    "Tecnología y Electrónica": [
        "Tienda de celulares y accesorios", "Venta y reparación de computadoras",
        "Tienda de electrodomésticos", "Venta de videojuegos y consolas", "Tienda de cámaras y fotografía"
    ],
    "Salud y Belleza": [
        "Farmacia", "Spa", "Spa y centro de masajes", "Estética y peluquería", "Barbería",
        "Tienda de cosméticos", "Tienda de productos naturales", "Suplementos", "Gym", "Pilates", "Box"
    ],
    "Hogar y Muebles": [
        "Tienda de muebles", "Ferretería", "Tienda de iluminación",
        "Venta de artículos de decoración", "Tienda de colchones"
    ],
    "Entretenimiento y Cultura": [
        "Librería", "Tienda de música e instrumentos", "Tienda de cómics y coleccionables",
        "Juguetería", "Cine o sala de entretenimiento"
    ],
    "Servicios y Oficinas": [
        "Agencia de viajes", "Inmobiliaria", "Consultoría empresarial", "Centro de copiado e impresión",
        "Agencia de publicidad y marketing", "Escuela de idiomas o cursos"
    ],
    "Deportes y Aire Libre": [
        "Tienda de artículos deportivos", "Tienda de pesca y camping", "Tienda de bicicletas y accesorios"
    ],
    "Mascotas": [
        "Tienda de alimentos y accesorios para mascotas", "Veterinaria", "Estética canina"
    ],
    "Jurídico y Legal": [
        "Despacho de abogados", "Notaría", "Correduría pública", "Mediación y arbitraje",
        "Agencia de cobranza", "Oficina de gestoría legal y trámites", "Consultoría en propiedad intelectual"
    ],
    "Finanzas y Contabilidad": [
        "Despacho contable", "Asesoría financiera", "Casa de bolsa", "Cooperativa de ahorro y préstamo",
        "Agencia de seguros", "Consultoría en inversiones", "Oficina de crédito y cobranza"
    ],
    "Salud y Medicina": [
        "Hospital", "Clínica médica", "Consultorio dental", "Laboratorio clínico",
        "Centro de rehabilitación", "Óptica", "Farmacia con consultorio",
        "Nutrición y dietética", "Psicología y terapia"
    ],
    "Hoteles y Hospedaje": [
        "Hotel de lujo", "Hotel boutique", "Hotel de negocios", "Motel", "Hostal",
        "Cabañas y ecoturismo", "Airbnb y alquiler vacacional", "Resort con todo incluido", "Glamping (camping de lujo)"
    ],
    "Seguridad y Protección Civil": [
        "Bomberos", "Protección Civil", "Policía Municipal", "Policía Estatal",
        "Policía Federal (Guardia Nacional)", "Seguridad Privada", "C5 o C4 (centro de monitoreo y emergencias)"
    ],
    "Rescate y Emergencias Médicas": [
        "Cruz Roja", "Cruz Ámbar", "ERUM", "Grupos de Rescate en Montaña y Mar",
        "Hospitales de Urgencias", "Centros de donación de sangre y trasplantes"
    ],
    "Dependencias de Gobierno": [
        "Presidencia de la República", "Secretaría de Hacienda y Crédito Público (SHCP)",
        "Secretaría de Educación Pública (SEP)", "Secretaría de Salud (SSA)",
        "Secretaría de Seguridad y Protección Ciudadana", "Secretaría de Relaciones Exteriores (SRE - pasaportes, consulados)",
        "Secretaría de Turismo", "Instituto Nacional de Migración (INM)",
        "Servicio de Administración Tributaria (SAT)", "Gobernatura del Estado",
        "Secretaría de Finanzas del Estado", "Secretaría de Movilidad y Transporte",
        "Secretaría de Desarrollo Económico", "Secretaría de Medio Ambiente",
        "Registro Civil (actas de nacimiento, matrimonio, defunción)", "Fiscalía General del Estado",
        "Presidencia Municipal", "Tesorería Municipal", "Desarrollo Urbano y Obras Públicas",
        "Seguridad Pública y Tránsito Municipal", "Servicios Públicos Municipales (agua, recolección de basura)",
        "DIF Municipal (Desarrollo Integral de la Familia)"
    ]
}


# Formula para calcular distancias con latitud y longitud 
def calcular_distancia_haversine(lat1, lon1, lat2, lon2):
    """
    Calcula la distancia en kilómetros entre dos puntos en la Tierra dados por sus latitudes y longitudes
    utilizando la fórmula de Haversine.
    """
    R = 6371  # Radio de la Tierra en km
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    delta_phi = math.radians(lat2 - lat1)
    delta_lambda = math.radians(lon2 - lon1)
    
    a = math.sin(delta_phi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    distancia = R * c  # Distancia en kilómetros
    
    return distancia
# Obtener los lugares pedidos atravez de la API de google Maps
def obtener_establecimientos_localidad(coordenadas, establecimientos, radius):
    """
    Obtiene una lista de establecimientos cercanos a las coordenadas proporcionadas,
    basándose en una lista de tipos de establecimientos.

    Args:
    coordenadas (tuple): Coordenadas de latitud y longitud (lat, lng).
    establecimientos (list): Lista de tipos de establecimientos a buscar (ej. ['restaurantes', 'cafeterías']).
    radius (Number): Radio de busqueda en Km

    Returns:
    pd.DataFrame: DataFrame con la información de los establecimientos encontrados.
    """
    import googlemaps
    import pandas as pd
    
    #api_key = 'AIzaSyD-8YtqddehkKMliUFm7hC7d_TQvaXrIHs'
    # Clave Kevin
    #api_key = 'AIzaSyDiNy34j9wI4gQ2hE1TjpbQVFcsoVNaYRo'

    # Clave julioolaf30@gmail.com
    #api_key = 'AIzaSyBWhqY-yTEdtLGDy-bdCrREOfgVphMe9sg'

    # Clave juliogonzalez.lizantos@gmail.com
    api_key = 'AIzaSyA5GlM3Rq9HWUauiSx2LEA2PzhMo-4k2GE'
    # Crear el cliente de Google Maps
    gmaps = googlemaps.Client(key=api_key)

    # Lista vacía para almacenar los datos de los establecimientos
    resultados_totales = []

    # Recorremos la lista de establecimientos que queremos buscar
    for establecimiento in establecimientos:
        # Realizar la búsqueda de los establecimientos en la localidad especificada
        try:
            resultados = gmaps.places(query=establecimiento, location=coordenadas, radius=radius*1000)  # Radio de búsqueda en metros
        except Exception as e:
            print(f"Error al realizar la consulta para {establecimiento}: {e}")
            continue

        # Lista vacía para almacenar los datos de los establecimientos de esta búsqueda
        establecimientos_localidad = []

        # Bucle para manejar la paginación y obtener todos los resultados
        while resultados:
            # Recorremos los resultados y extraemos la información necesaria
            for idx, lugar in enumerate(resultados.get('results', [])):
                nombre = lugar.get('name', 'No disponible')
                direccion = lugar.get('formatted_address', 'No disponible')
                lat = lugar.get('geometry', {}).get('location', {}).get('lat', 'No disponible')
                lng = lugar.get('geometry', {}).get('location', {}).get('lng', 'No disponible')

                if lat == 'No disponible' or lng == 'No disponible':
                    continue

                # Calcular la distancia desde el centro de búsqueda
                distancia = calcular_distancia_haversine(coordenadas[0], coordenadas[1], lat, lng)

                # Solo agregar el establecimiento si está dentro del radio de búsqueda
                if distancia <= radius:  # El radio es en km
                    coordenadas_str = f"{lat}, {lng}"

                    # Crear la URL de Google Maps
                    liga_google_maps = f"https://www.google.com/maps?q={lat},{lng}"

                    # Añadir la información al listado
                    establecimientos_localidad.append([idx + 1, nombre, establecimiento, direccion, lat, lng, liga_google_maps, ])

            # Verificamos si hay más resultados a través del 'next_page_token'
            next_page_token = resultados.get('next_page_token')
            if next_page_token:
                time.sleep(2)  # Pausar para evitar bloqueos debido al tiempo de espera recomendado
                resultados = gmaps.places(query=establecimiento, location=coordenadas, radius=radius*1000, page_token=next_page_token)
            else:
                break  # Si no hay más resultados, salimos del ciclo

        # Añadimos los resultados de este establecimiento a la lista total
        resultados_totales.extend(establecimientos_localidad)

    # Crear un DataFrame de pandas con los datos recopilados
    df = pd.DataFrame(resultados_totales, columns=["ID","Nombre de establecimiento", "Establecimiento", "Domicilio", "latitud", "longitud", "Liga de Google Maps", ])

    return df

# Función para obtener la calificación y el tipo de cocina de un restaurante
def obtener_informacion_restaurante(row,columna):
    import requests
    import pandas as pd
    # Tu clave API de Google Places (reemplázala por la clave real)
    api_key = 'AIzaSyD-8YtqddehkKMliUFm7hC7d_TQvaXrIHs'
    restaurante = row[columna]  
    
    # URL de la API de Google Places
    url = "https://maps.googleapis.com/maps/api/place/textsearch/json"
    
    # Parámetros para la búsqueda
    params = {
        'query': restaurante,
        'key': api_key
    }
    
    try:
        # Hacer la solicitud a la API
        response = requests.get(url, params=params)
        data = response.json()
        
        # Verifica si hay resultados
        if data['status'] == 'OK':
            lugar = data['results'][0]
            
            # Obtener la calificación
            calificacion = lugar.get('rating', 'No disponible')
            
            # Obtener los tipos de cocina
            tipos = lugar.get('types', [])
            
            # Clasificación basada en tipos de cocina
            if "fine_dining" in tipos:
                tipo_cocina = 'Alta Cocina'
            elif "restaurant" in tipos and "casual" in tipos:
                tipo_cocina = 'Cocina Casual'
            elif "fast_food" in tipos:
                tipo_cocina = 'Comida Rápida'
            elif "cafe" in tipos:
                tipo_cocina = 'Cafetería'
            else:
                tipo_cocina = 'Otro Tipo de Cocina'
            
            # Obtener el tipo de lugar (categoría del restaurante)
            tipo_lugar = lugar.get('types', ['Tipo no disponible'])[0]  # Tomar el primer tipo si hay más de uno
            
            # Devolver los valores para el DataFrame
            return pd.Series([calificacion, tipo_cocina, tipo_lugar])
        else:
            return pd.Series(['No disponible', 'Tipo no disponible', 'Tipo no disponible'])
    except Exception as e:
        return pd.Series(['Error al obtener datos', 'Error al obtener datos', 'Error al obtener datos'])

# Grafico interactivo
def graficar_interactivo(df,colum_to_grafic,title_grafica, name_grafica, tipo_grafica='bar'):
    """
    Genera un gráfico interactivo que muestra el total de cada tipo de comercio,
    y permite hacer clic para obtener más detalles. Se puede seleccionar el tipo de gráfico.

    Args:
    df (pd.DataFrame): DataFrame con los datos de los establecimientos.
    colum_to_grafic (srt): Nombre de la columna a graficar.
    title_grafica (str): Título de la gráfica.
    name_grafica (str): Nombre con el que se guardará el archivo de la gráfica.
    tipo_grafica (str): Tipo de gráfico a generar ('bar', 'pie', etc.). Por defecto es 'bar'.
    """
    # Contamos cuántos establecimientos hay por tipo
    conteo_comercios = df[colum_to_grafic].value_counts().reset_index()
    conteo_comercios.columns = [colum_to_grafic, 'Cantidad']

    # Definir la paleta de colores pastel
    pastel_colors = ['#FFB3BA', '#FFDFBA', '#FFFFBA', '#BAFFC9', '#BAE1FF', '#D9D9D9', '#FFB3D9']

    # Elegir el tipo de gráfico basado en el argumento `tipo_grafica`
    if tipo_grafica == 'bar':
        fig = px.bar(conteo_comercios, 
                     x=colum_to_grafic, 
                     y='Cantidad', 
                     title=title_grafica,
                     labels={colum_to_grafic: colum_to_grafic, 'Cantidad': "Total"},
                     color=colum_to_grafic,  # Los colores se asignarán a cada tipo
                     color_discrete_sequence=pastel_colors)  # Paleta pastel
    elif tipo_grafica == 'pie':
        fig = px.pie(conteo_comercios, 
                     names=colum_to_grafic, 
                     values='Cantidad', 
                     title=title_grafica,
                     color_discrete_sequence=pastel_colors)  # Paleta pastel
        
        # Personalizar el hovertemplate para mostrar el nombre de la categoría y porcentaje
        fig.update_traces(
            hovertemplate="<b>%{label}</b><br>" +  # Mostrar el nombre de la categoría
                          "Cantidad: %{value}<br>" +    # Mostrar la cantidad
                          "Porcentaje: %{percent:.2%}<br>" +  # Mostrar el porcentaje
                          "<extra>Haga clic para ver los detalles</extra>"
        )
        
    elif tipo_grafica == 'line':
        fig = px.line(conteo_comercios, 
                      x=colum_to_grafic, 
                      y='Cantidad', 
                      title=title_grafica,
                      labels={colum_to_grafic: colum_to_grafic, 'Cantidad': 'Total'},
                      line_shape='linear')
    else:
        raise ValueError("El tipo de gráfico no es válido. Usa 'bar', 'pie' o 'line'.")

    # Estilo de fuentes personalizado
    fig.update_layout(
        title_font=dict(family="Lato, sans-serif", size=24, color='black'),
        font=dict(family="Roboto, sans-serif", size=14, color='black'),
        annotations=[dict(
            text="", 
            x=0.5, y=0.95, 
            xref="paper", yref="paper", 
            showarrow=False, 
            font=dict(family="Playfair Display, serif", size=18, color="#FF0000", style="italic")
        )],
        clickmode='event+select',
        title=title_grafica.upper()  # Convertir el título a mayúsculas
    )
    
    # Mostrar la gráfica
    fig.show()

    # Guardar la gráfica como un archivo HTML
    fig.write_html(name_grafica)
# ---------------------------------------------- #




# ---- Obtener claves de estado y municipio ---- #

# Carga global del archivo para evitar lecturas repetidas en cada llamada a la función
CLAVES_PATH = Path("C:/Users/julio/OneDrive/Documentos/Trabajo/Ideas Frescas/MacroDatos/Claves_Estado_Municipio.xlsx")
DF_CLAVES = pd.read_excel(CLAVES_PATH)

def obtener_claves_estado_mun(nombre_entidad, nombre_municipio=None, retornar_resultado=False):
    """
    Devuelve claves de entidad y municipio si se solicita explícitamente.
    En caso de error o error tipográfico, sugiere o informa el problema.

    Parámetros:
    ------------
    nombre_entidad : str
        Nombre del estado (ej. "Sinaloa").
    nombre_municipio : str, opcional
        Nombre del municipio (ej. "Mazatlán").
    retornar_resultado : bool
        Si es True, retorna un dict con claves correctas. Si es False, solo reporta errores/sugerencias.

    Retorna:
    --------
    None si todo está correcto y retornar_resultado=False.
    Mensaje de error o sugerencia si hay problemas.
    Claves {'CVE_ENT': ..., 'CVE_MUN': ...} si retornar_resultado=True.
    """
    nombre_entidad = nombre_entidad.strip().title()
    nombre_municipio = nombre_municipio.strip().title() if nombre_municipio else None

    entidades = DF_CLAVES['NOM_ENT'].unique()

    if nombre_entidad not in entidades:
        sugerencias = get_close_matches(nombre_entidad, entidades, n=1, cutoff=0.6)
        return f"¿Quisiste decir '{sugerencias[0]}'?" if sugerencias else f"'{nombre_entidad}' no es válido."

    df_ent = DF_CLAVES[DF_CLAVES['NOM_ENT'] == nombre_entidad]

    if not nombre_municipio:
        return {'CVE_ENT': df_ent['CVE_ENT'].iloc[0]} if retornar_resultado else None

    municipios = df_ent['NOM_MUN'].unique()

    if nombre_municipio not in municipios:
        sugerencias = get_close_matches(nombre_municipio, municipios, n=1, cutoff=0.6)
        if sugerencias:
            return f"¿Quisiste decir '{sugerencias[0]}'?"
        mun_otro = DF_CLAVES[DF_CLAVES['NOM_MUN'] == nombre_municipio]
        if not mun_otro.empty:
            estados = mun_otro['NOM_ENT'].unique()
            return f"'{nombre_municipio}' pertenece a: {', '.join(estados)}"
        return f"'{nombre_municipio}' no se encontró en ningún estado."

    fila = df_ent[df_ent['NOM_MUN'] == nombre_municipio].iloc[0]
    return {'CVE_ENT': fila['CVE_ENT'], 'CVE_MUN': fila['CVE_MUN']} if retornar_resultado else None
# ---------------------------------------------- #




# ---- Ingresos y gastos ---- #
# Ingresos Promedio por Estado, Municipio NSE
def NSE_CONEVAL(estado=None, municipio=None):
    import pandas as pd
    from pathlib import Path
    # Ruta relativa al archivo CSV
    csv_pobreza = Path("..") / "MacroDatos" / "pobreza22.csv"
    
    # Cargar la base de datos CONEVAL
    pobreza_data = pd.read_csv(csv_pobreza)
    
    # Filtrar por estado si se proporciona
    if estado is not None:
        pobreza_data = pobreza_data[pobreza_data['ent'] == estado]
    
    # Filtrar por municipio si se proporciona
    if municipio is not None:
        pobreza_data = pobreza_data[pobreza_data['ubica_geo'] == municipio]
    
    # Definir los intervalos y las etiquetas
    bins = [0, 4000, 9000, 18000, 30000, 45000, 100000, float('inf')]
    labels = ['E', 'D', 'D+', 'C-','C', 'C+', 'A/B']
    
    # Crear la nueva columna 'Nivel Socioeconomico'
    pobreza_data['Nivel Socioeconomico'] = pd.cut(
        pobreza_data['ictpc'],
        bins=bins,
        labels=labels,
        right=False  # Incluye el límite inferior en el intervalo
    )
    
    # Filtrar por las columnas que necesitamos
    ingreso_prom = pobreza_data[['ictpc', 'Nivel Socioeconomico']]
    
    # Agrupar por 'Nivel Socioeconomico' y calcular la media de 'ictpc'
    ingreso_promedio = ingreso_prom.groupby('Nivel Socioeconomico', observed=False)['ictpc'].mean().reset_index()
    
    # Definir las etiquetas
    labels = ['E', 'D', 'D+', 'C-', 'C', 'C+', 'A/B']
    
    # Filtrar y ordenar los resultados según las etiquetas
    ingreso_promedio = ingreso_promedio[ingreso_promedio['Nivel Socioeconomico'].isin(labels)]
    ingreso_promedio = ingreso_promedio.set_index('Nivel Socioeconomico').reindex(labels).reset_index()
    
    # Renombrar las columnas
    ingreso_promedio.columns = ['Nivel Socioeconomico', 'Ingreso Promedio']
    
    # Calcular el promedio general de 'ictpc'
    promedio_general = ingreso_prom['ictpc'].mean()

    ingreso_promedio['Ingreso Promedio'] = ingreso_promedio['Ingreso Promedio'].round(2)

    # Calcular el porcentaje de cada nivel socioeconómico a nivel municipio
    suma = pobreza_data['Nivel Socioeconomico'].value_counts().sum()
    valores = pobreza_data['Nivel Socioeconomico'].value_counts().tolist()
    
    ingreso_promedio["Porcentaje"] = (valores / suma) * 100

    # Definir el nombre del archivo CSV
    if estado is None and municipio is None:
        # Si no hay filtro (todos los datos)
        csv_nombre = "Nivel Socioeconomico a nivel nacional.csv"
    elif municipio is None:
        # Si solo se filtra por estado
        csv_nombre = f"Nivel Socioeconomico del estado.csv"
    else:
        # Si se filtra por estado y municipio
        csv_nombre = f"Nivel Socioeconomico del estado y del municipio.csv"
    
    # Crear el CSV de la base de datos filtrados
    ingreso_promedio.to_csv(csv_nombre, index=False, encoding='ISO-8859-1')

    return ingreso_promedio

# Obtener los Niveles Socioeconomicos AMAI 2024
def nivel_socioeconomico_AMAI(estado, municipio, zona_total_estudio):
    """
    Función que procesa y filtra los datos socioeconómicos por AGEB de un municipio específico dentro de un estado
    utilizando la base de datos 'NSE_por_AGEB_AMAI.xlsx'. La función valida que el 'estado' y el 'municipio' sean
    correctos antes de realizar el procesamiento de los datos.

    Argumentos:
    estado (str): El nombre del estado a filtrar en la base de datos.
    municipio (str): El nombre del municipio a filtrar en la base de datos.
    zona_total_estudio (DataFrame):lista de AGEB a filtrar. 

    Salida:
    CSVs generados:
    - 'Nivel_Socioeconomico_por_AGEB_{municipio}_{estado}.csv' con los datos filtrados.
    - 'Nivel_Socioeconomico_por_AGEB_{municipio}_{estado}_agrupado.csv' con los datos agrupados por 'AGEB'.
    """

    import pandas as pd
    import numpy as np
    from pathlib import Path

    # Ruta relativa al archivo Excel
    nse_amai = Path("..") / "MacroDatos" / "NSE_por_AGEB_AMAI.xlsx"
    
    # Leer el archivo Excel
    data_amai = pd.read_excel(nse_amai)

    # Renombrar las columnas para que coincidan con las expectativas
    data_amai.rename(columns={
        'TOTAL DE VIVIENDAS POR NIVEL SOCIOECONÓMICO': 'AB',
        'Unnamed: 8': 'C+',
        'Unnamed: 9': 'C',
        'Unnamed: 10': 'C-',
        'Unnamed: 11': 'D+',
        'Unnamed: 12': 'D',
        'Unnamed: 13': 'E'}, inplace=True)

    # Eliminar la primera fila (probablemente de encabezado)
    data_amai = data_amai.drop(index=0)

    # Filtrar las columnas relevantes
    data_amai_zona_estudio = data_amai[[
        'NOMBRE ENTIDAD',
        'NOMBRE MUNICIPIO',
        'NOMBRE LOCALIDAD',
        'AGEB',
        'AB',
        'C+',
        'C',
        'C-',
        'D+',
        'D',
        'E',
        'NIVEL PREDOMINANTE'
    ]]

    # Validar que el estado esté en la lista de estados disponibles
    if estado not in data_amai['NOMBRE ENTIDAD'].unique().tolist():
        raise ValueError(f"El estado '{estado}' no está en la base de datos. Los estados disponibles son: "
                         f"{data_amai['NOMBRE ENTIDAD'].unique().tolist()}")

    # Filtrar por Estado
    data_amai_zona_estudio = data_amai_zona_estudio[data_amai_zona_estudio['NOMBRE ENTIDAD'] == estado]

    # Validar que el municipio esté en la lista de municipios disponibles para el estado dado
    municipios_disponibles = data_amai_zona_estudio['NOMBRE MUNICIPIO'].unique().tolist()
    if municipio not in municipios_disponibles:
        raise ValueError(f"El municipio '{municipio}' no está en el estado '{estado}'. Los municipios disponibles en "
                         f"este estado son: {municipios_disponibles}")

    # Filtrar por Municipio
    data_amai_zona_estudio = data_amai_zona_estudio[data_amai_zona_estudio['NOMBRE MUNICIPIO'] == municipio]

    # Realizar el merge con el DataFrame de zona_total_estudio usando la columna 'AGEB'
    data_amai_zona_estudio = data_amai_zona_estudio[data_amai_zona_estudio['AGEB'].isin(zona_total_estudio)]

    # Guardar el CSV con los datos filtrados
    data_amai_zona_estudio.to_csv(f"Nivel_Socioeconomico_por_AGEB_de_interes_en_{municipio}_{estado}.csv", index=False, encoding='ISO-8859-1')

    return data_amai_zona_estudio

# Gastohogar zona de estudio    
def gastohogar_zona_estudio(estado: str, municipio: str, localidad: str):
    """
    Esta función procesa y clasifica los datos del archivo 'enigh_gastohogar.csv' 
    en función de las categorías de gasto y filtra los datos por Estado, Municipio 
    y Localidad. Los resultados se guardan en un archivo CSV y se retornan como 
    un DataFrame para su posterior uso.

    Parámetros:
    estado (str): Código del Estado para filtrar los datos.
    municipio (str): Código del Municipio para filtrar los datos.
    localidad (str): Código de la Localidad para filtrar los datos.

    Retorna:
    DataFrame: Un DataFrame con los datos filtrados y clasificados por gasto.
    """

    import pandas as pd
    from pathlib import Path

    # Ruta al archivo CSV
    gastohogar_path = Path("C:/Users/julio_2h7cnt5/OneDrive/Documentos/Ideas Frescas/MacroDatos/enigh_gastohogar.csv")
    
    # Cargar el archivo CSV
    gastohogar = pd.read_csv(gastohogar_path) 
    
    # Asegurarnos de que la columna 'folioviv' esté en formato string
    gastohogar['folioviv'] = gastohogar['folioviv'].astype(str)
    
    # Separar la columna 'folioviv' en las 5 nuevas variables
    gastohogar['Estado'] = gastohogar['folioviv'].str.slice(0, 2)
    gastohogar['Municipio'] = gastohogar['folioviv'].str.slice(2, 5)
    gastohogar['Localidad'] = gastohogar['folioviv'].str.slice(5, 8)
    gastohogar['Manzana'] = gastohogar['folioviv'].str.slice(8, 10)
    gastohogar['Numero_orden'] = gastohogar['folioviv'].str.slice(10, 13)
    
    # Seleccionar las columnas relevantes
    cols = ['folioviv', 'Estado', 'Municipio', 'Localidad', 'Manzana']
    cols_extra = [col for col in gastohogar.columns if col not in cols]
    gastohogar = gastohogar[cols + cols_extra]

    # Filtrar los datos según los parámetros de entrada
    gastohogar_filtrado = gastohogar[(gastohogar['Estado'] == estado) & 
                                     (gastohogar['Municipio'] == municipio) & 
                                     (gastohogar['Localidad'] == localidad)]

    # Definir la función de clasificación de gasto
    def clasificar_gasto(clave):
        if clave.startswith('A') or clave == 'T901':
            return 'Alimentos'
        elif clave.startswith('C') or clave == 'T903':
            return 'Limpieza'
        elif clave.startswith('D'):
            return 'Personales'
        elif clave in ['F002', 'R007', 'L029', 'N001', 'N002', 'N012', 'N013', 'N014', 'N015', 'T904']:
            return 'Personales'
        elif clave.startswith('E') or clave == 'T905':
            return 'Educación'
        elif clave.startswith('B') or clave.startswith('M'):
            return 'Transporte'
        elif clave in ['F001', 'F003', 'F004', 'F005', 'F006', 'F007', 'F008', 'F009', 'F010', 'F011', 'F012', 'F013', 'F014', 'R005', 'R006', 'R012', 'L013', 'N008', 'N016', 'T902', 'T914', 'T906']:
            return 'Transporte'
        elif clave.startswith('G') or clave.startswith('I') or clave.startswith('K'):
            return 'Vivienda'
        elif clave in ['R001', 'R002', 'R003', 'R004', 'R008', 'R009', 'R010', 'R011', 'R013', 'N005', 'N007', 'N009', 'T907', 'T910', 'T912']:
            return 'Vivienda'
        elif clave.startswith('H') or clave == 'T909':
            return 'Vestido y Calzado'
        elif clave.startswith('J') or clave == 'T911':
            return 'Salud'
        elif clave in ['L001', 'L002', 'L003', 'L004', 'L005', 'L006', 'L007', 'L008', 'L009', 'L010', 'L011', 'L012', 'L014', 'L015', 'L017', 'L018', 'L019', 'L020', 'L021', 'L022', 'L023', 'L024', 'L025', 'L026', 'L027', 'L028', 'N003', 'N006', 'T913']:
            return 'Esparcimiento'
        elif clave == 'N004':
            return 'Turísticos'
        elif clave in ['N010', 'N011', 'T908', 'T915', 'T916']:
            return 'Transparencias de Gasto'
        return 'Otro'

    # Aplicar la función de clasificación de gasto
    gastohogar_filtrado['Clasificación gasto'] = gastohogar_filtrado['clave'].apply(clasificar_gasto)

    # Reordenar las columnas
    columnas = ['folioviv', 'Estado', 'Municipio', 'Localidad', 'Manzana', 'clave', 
                'Clasificación gasto', 'tipo_gasto', 'forma_pag1', 'forma_pag2', 
                'forma_pag3', 'lugar_comp', 'frecuencia', 'cantidad', 'gasto']
    gastohogar_filtrado = gastohogar_filtrado[columnas]

    # Crear el nombre del archivo CSV a partir de los parámetros de entrada
    nombre_archivo = f"gastohogar_{estado}_{municipio}_{localidad}.csv"
    
    # Guardar el DataFrame en un archivo CSV
    gastohogar_filtrado.to_csv(nombre_archivo, index=False)

    # Retornar el DataFrame para seguir trabajando con él
    return gastohogar_filtrado
# ---------------------------------------------- #



# --- CONFIGURACIÓN ---
def conectar_mongo(uri="mongodb://localhost:27017/", db_name="puertos_cruceros", collection_name="arribos_pasajeros"):
    client = MongoClient(uri)
    db = client[db_name]
    return db[collection_name]

# --- CONSULTA POR PUERTO ---
def consultar_puerto(puerto, coleccion=None):
    if coleccion is None:
        coleccion = conectar_mongo()
    resultados = list(coleccion.find({"Puerto": puerto}))
    return pd.DataFrame(resultados)

# --- CONSULTAR TODOS LOS REGISTROS ---
def obtener_todo(coleccion=None):
    if coleccion is None:
        coleccion = conectar_mongo()
    return pd.DataFrame(list(coleccion.find()))

# --- AGREGAR NUEVOS REGISTROS ---
def agregar_datos(df_nuevo, coleccion=None):
    if coleccion is None:
        coleccion = conectar_mongo()

    if not isinstance(df_nuevo, pd.DataFrame):
        raise ValueError("Debe proporcionar un DataFrame de pandas.")

    registros = df_nuevo.to_dict(orient='records')
    resultado = coleccion.insert_many(registros)
    print(f"{len(resultado.inserted_ids)} registros insertados.")

# --- EJEMPLO DE USO DIRECTO ---
if __name__ == "__main__":
    # Conexión a MongoDB
    coleccion = conectar_mongo()

    # Consultar por puerto
    df = consultar_puerto("Mazatlán, Sin.", limite=5, coleccion=coleccion)
    print("Primeros registros de Mazatlán:")
    print(df)

    # Consultar toda la base (opcional)
    # df_todo = obtener_todo(coleccion)
    # print(df_todo.head())

    # Agregar nuevos datos (opcional)
    # df_nuevo = pd.read_csv("nuevos_datos.csv")
    # agregar_datos(df_nuevo, coleccion)
