import os import pandas as pd import csv import plotly.express as px import gradio as gr import folium from folium.plugins import MarkerCluster # Función para cargar CSV de oficinas def load_offices_csv(path): with open(path, encoding='latin-1') as f: sample = ''.join([next(f) for _ in range(10)]) dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|']) sep = dialect.delimiter print(f"Delimitador detectado: '{sep}'".replace('{sep}', sep)) df = pd.read_csv(path, sep=sep, encoding='latin-1', engine='python', on_bad_lines='skip') df.columns = [c.strip() for c in df.columns] return df # Carga y preparación de datos DATA_XLSX = "Base de Datos Prueba.xlsx" OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv" df = pd.read_excel(DATA_XLSX, parse_dates=["FECHA_APERTURA"]) df["MES"] = df["FECHA_APERTURA"].dt.to_period("M").dt.to_timestamp() off_meta = load_offices_csv(OFFICES_CSV) # Listas estáticas iniciales (convertir a str para evitar mezclas int/str) departamentos = sorted(off_meta['DEPARTAMENTO'].dropna().astype(str).unique().tolist()) municipios = sorted(off_meta['CIUDAD'].dropna().astype(str).unique().tolist()) zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist()) productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist()) colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist()) segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist()) min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max()) min_plazo, max_plazo= int(df['PLAZO'].min()), int(df['PLAZO'].max()) # Coordenadas de oficinas office_coords = { "Abrego": (8.080040, -73.219050), "Aguachica": (8.310229, -73.599837), "Aguazul": (5.171710, -72.547300), "Agustin Codazzi": (10.033471, -73.291284), "Andes": (5.655900, -75.879512), "Apartado": (7.885610, -76.634790), "Arauca": (7.086173, -70.757347), "Arjona": (10.297838, -75.308821), "Armenia": (6.167542, -75.764031), "Av. Caracas": (4.582934, -74.092877), "Ayapel": (0.000000, 0.000000), "Barrancabermeja": (7.059381, -73.862874), "Barrancas": (10.956670, -72.788870), "Barranquilla": (10.961041, -74.800959), "Belen": (5.989720, -72.913860), "Bello": (6.334930, -75.558280), "Bienestar Social Empleados": (0.000000, 0.000000), "Bosa": (5.930960, -73.616320), "Bosconia": (9.972841, -73.885721), "Bucaramanga Centro": (7.092868, -73.126498), "Buenaventura": (3.877616, -77.007365), "Buenos Aires": (3.013970, -76.646120), "Buga": (6.254484, -75.563634), "Cachingos": (0.000000, 0.000000), "Caldas": (6.089999, -75.636627), "Cali El Poblado (NO VIGENTE)": (3.418618, -76.497226), "Cali Norte": (6.300640, -70.205430), "Cali Sur": (0.000000, 0.000000), "Canal C": (0.000000, 0.000000), "Candelaria": (3.408325, -76.349086), "Carepa": (7.757548, -76.656274), "Carmen de Viboral": (6.082360, -75.335090), "Cartagena": (0.000000, 0.000000), "Cartagena Sur (NO VIGENTE)": (0.000000, 0.000000), "Cartago": (4.746743, -75.913598), "Castilla": (3.827220, -73.688310), "Caucasia": (7.987347, -75.196855), "Centro Bogota (NO VIGENTE)": (0.000000, 0.000000), "Centro MedelliAn": (6.254484, -75.563634), "Centro Suba": (5.828915, -73.035056), "Cerrito": (6.842993, -72.694730), "Chaparral": (3.750151, -75.340480), "Chia": (4.859712, -74.059663), "Chinacota": (7.607990, -72.600380), "Chinchina": (4.984375, -75.604801), "Chiquinquira": (5.618910, -73.819970), "Cienaga de Oro": (8.874380, -75.621750), "Cimitarra": (6.316110, -73.950540), "Copacabana": (6.348513, -75.507062), "Corozal": (9.317780, -75.295830), "Cucuta Atalaya": (7.907352, -72.524705), "Cucuta Centro": (7.884556, -72.504855), "Dabeiba": (7.033533, -76.167771), "Direccion General": (0.000000, 0.000000), "Duitama": (5.807690, -73.070165), "El Bagre": (7.596620, -74.804880), "El Banco": (8.998900, -73.970580), "El Cable (NO VIGENTE)": (0.000000, 0.000000), "El Tejar (NO VIGENTE)": (0.000000, 0.000000), "Engativa": (6.254484, -75.563634), "Envigado": (6.173196, -75.592097), "Espinal (NO VIGENTE)": (0.000000, 0.000000), "Facatativa": (4.811561, -74.384369), "Florencia": (1.682220, -77.072610), "Florida": (3.324420, -76.235460), "Floridablanca": (7.079171, -73.108311), "Fonseca": (10.888700, -72.851530), "Fontibon": (6.777790, -76.128580), "Fundacion": (10.521380, -74.186640), "Fundadores (NO VIGENTE)": (0.000000, 0.000000), "Funza": (4.714865, -74.212997), "Fusagasuga": (4.311530, -74.355406), "Galan": (6.637810, -73.288780), "Garagoa": (5.083373, -73.363727), "Garzon": (2.195783, -75.629006), "Gerencia Territorial - Sur": (0.000000, 0.000000), "Girardot": (4.303965, -74.804788), "Giron": (7.074196, -73.167534), "Granada": (4.519657, -74.353677), "Guaduas": (5.067815, -74.598816), "Ibague": (4.325569, -75.072920), "Ibague Centro (NO VIGENTE)": (0.000000, 0.000000), "Ipiales": (0.836103, -77.679298), "Itagui": (6.170260, -75.616540), "Jamundi": (3.111418, -76.606186), "Kennedy": (4.622480, -74.150010), "La America": (5.400098, -75.546666), "La Calera": (4.721216, -73.968126), "La Ceja": (6.032140, -75.431942), "La Dorada": (5.464481, -74.704063), "La Libertad": (2.445261, -76.632240), "La Plata": (2.391670, -75.891670), "La Union": (8.857282, -75.277048), "La Union - Valle": (4.537120, -76.104421), "La Victoria": (-0.111490, -71.110860), "Leticia": (-4.215596, -69.939065), "Libano": (4.922540, -75.063700), "Lorica (NO VIGENTE)": (0.000000, 0.000000), "Magangue": (9.186535, -74.788838), "Maicao": (11.350383, -72.352333), "Malaga": (6.702446, -72.731766), "Manizales": (5.050927, -75.519500), "Manrique": (6.484975, -75.019537), "Marinilla": (6.173840, -75.334800), "Mariquita": (5.198660, -74.896950), "Mesitas": (3.383561, -74.044270), "Minorista": (0.000000, 0.000000), "Mitu": (1.255250, -70.233390), "MoAitos": (0.000000, 0.000000), "Mocoa": (1.148930, -76.647750), "Mod Empoderados Costa Norte": (0.000000, 0.000000), "Mod Empoderados Plus-Bta": (0.000000, 0.000000), "Mod Empoderados Plus-M/llin": (0.000000, 0.000000), "Mod Empoderados Plus-Sur": (0.000000, 0.000000), "Molinos": (10.701780, -74.716750), "Mompox": (0.000000, 0.000000), "Moniquira": (5.877280, -73.570440), "MonteliAbano": (7.983010, -75.417260), "MonteriAa Centro": (8.754728, -75.881810), "Monteria": (8.773391, -75.817808), "Neiva (NO VIGENTE)": (0.000000, 0.000000), "Niquia": (2.649380, -75.636650), "OcaAa": (8.233420, -73.353310), "Orito": (0.665371, -76.872392), "PE Soledad Malambo": (10.861604, -74.773950), "PE Acacias": (3.987212, -73.765837), "PE Anserma": (5.230240, -75.787920), "PE Baranoa": (10.796676, -74.914419), "PE Belen": (5.989720, -72.913860), "PE Cali La Casona": (0.000000, 0.000000), "PE Cartagena del ChairA": (1.334860, -74.843460), "PE Dosquebradas": (4.839160, -75.667270), "PE Istmina": (0.000000, 0.000000), "PE La Pintada 2": (5.749960, -75.616299), "PE Majagual": (8.534730, -74.657180), "PE Pasto (NO VIGENTE)": (0.000000, 0.000000), "PE Puerto Wilches": (7.348801, -73.898273), "PE PurificaciA3n": (3.856779, -74.932103), "PE Santa Rosa del Sur": (7.946502, -74.026033), "PE TAoquerres": (0.000000, 0.000000), "PE Tame": (6.470290, -71.716970), "PE Valle del Guamuez": (0.452500, -76.919170), "PE Zarzal": (4.395820, -76.069830), "Pailitas": (8.957360, -73.623460), "Palmira": (3.538908, -76.298466), "Pamplona": (7.377155, -72.648957), "Parque de la CaAa (NO VIGENTE)": (0.000000, 0.000000), "Parque de las Luces (NO VIGENTE)": (0.000000, 0.000000), "Pasto": (0.000000, 0.000000), "Patio Bonito": (6.205447, -75.575411), "Paz de Ariporo": (5.881428, -71.891972), "Pedregal": (6.254484, -75.563634), "Perdomo": (2.888170, -75.433810), "Pereira": (4.812216, -75.692047), "Piedecuesta": (6.988034, -73.050030), "PiendamA3 (NO VIGENTE)": (0.000000, 0.000000), "Pitalito": (1.790464, -76.055636), "Pivijay": (10.447166, -74.408568), "Planeta Rica": (8.408920, -75.586800), "Plato": (9.791910, -74.782970), "Popayan Norte": (2.462388, -76.535919), "Popayan Sur": (2.462388, -76.535919), "Primero de Mayo": (5.821771, -73.043034), "Principal": (13.375420, -81.369090), "Puerto Asis": (0.497650, -76.497680), "Puerto Berrio": (6.490949, -74.402668), "Puerto Boyaca": (5.971557, -74.571408), "Puerto CarreAo": (6.190854, -67.484779), "Puerto IniArida": (-2.148770, -71.754990), "Puerto Libertador": (7.889560, -75.672370), "Punto Express Neiva 2": (0.000000, 0.000000), "Quibdo": (0.000000, 0.000000), "Quirigua": (2.649380, -75.636650), "Restrepo": (3.826566, -76.521106), "Riohacha": (11.381478, -72.905309), "Rionegro": (7.264871, -73.147840), "Riosucio": (5.421350, -75.703230), "Robledo (NO VIGENTE)": (0.000000, 0.000000), "Sahagun": (8.941133, -75.495272), "San AndrAs Isla": (6.809803, -72.849736), "San Andres de Sotavento": (9.144750, -75.508770), "San Bernardo": (4.178771, -74.421565), "San Fernando": (9.218060, -74.330294), "San Francisco (NO VIGENTE)": (0.000000, 0.000000), "San Gil": (6.557700, -73.133180), "San Jose del Guaviare": (2.562393, -72.640344), "San Juan": (4.461030, -73.680480), "San Marcos": (8.658430, -75.131200), "San Onofre": (9.737530, -75.525580), "San Pelayo (NO VIGENTE)": (0.000000, 0.000000), "San Vicente del Caguan": (2.113170, -74.769180), "San Vicente del Chucuri": (6.881050, -73.411570), "Santa Helenita": (10.325410, -74.961830), "Santa MariAa (NO VIGENTE)": (0.000000, 0.000000), "Santa Marta Av. Libertador": (11.231008, -74.175841), "Santa Rosa de Cabal": (4.890245, -75.626971), "Santafe de Antioquia": (6.556870, -75.828060), "Santander de Quilichao": (3.008790, -76.485900), "Santo Domingo": (6.472363, -75.164506), "Sincelejo": (9.300213, -75.395603), "Sincelejo Centro (NO VIGENTE)": (9.303609, -75.392834), "Soacha": (4.582123, -74.211534), "Sogamoso": (5.718314, -72.930984), "Soledad (NO VIGENTE)": (0.000000, 0.000000), "Suba": (5.451280, -73.814140), "Suba Rincon": (4.728370, -74.088350), "Tesoreria": (0.000000, 0.000000), "Tierralta": (8.173570, -76.059210), "ToberiAn": (0.000000, 0.000000), "Tulua": (4.084320, -76.196650), "Tumaco": (0.000000, 0.000000), "Tunja": (5.538590, -73.366380), "Turbo": (8.098040, -76.731690), "Ubate": (4.482420, -73.934840), "Urrao": (6.340116, -76.097593), "Valledupar": (10.469026, -73.257035), "Valledupar Centro": (10.476217, -73.245945), "Velez": (6.012930, -73.673140), "Venecia": (4.088080, -74.477460), "Villanueva": (4.609834, -72.927380), "Villavicencio": (4.144229, -73.634525), "Villeta": (5.011370, -74.471560), "Yarumal": (6.962765, -75.416779), "Yomasa": (4.519873, -74.092186), "Yopal": (5.340170, -72.394240), "Yumbo": (3.581378, -76.494648), "Zipaquira": (5.025810, -73.991283), } # Callbacks def update_municipios(dept): return sorted(off_meta.dropna().astype(str).unique().tolist()) def update_zonas(dept, muni): df2 = off_meta return sorted(df2['ZONA'].dropna().astype(str).unique().tolist()) def update_oficinas(dept, muni, zona): if zona: df2 = df2[df2['ZONA']==zona] return sorted(df2['NOMBRE OFICINA'].dropna().astype(str).unique().tolist()) # Dashboard def dashboard(f_inicio, f_fin, zona, tipos, colaborador_sel, segmento_sel): d = df.copy() if f_inicio: d = d[d['FECHA_APERTURA'] >= pd.to_datetime(f_inicio)] if f_fin: d = d[d['FECHA_APERTURA'] <= pd.to_datetime(f_fin)] if tipos: d = d[d['TIPO PRODUCTO'].astype(str).isin(tipos)] if colaborador_sel: d = d[d['SK_COLABORADOR'].astype(str).isin(colaborador_sel)] if segmento_sel: d = d[d['SEGMENTO_CLIENTE'].astype(str).isin(segmento_sel)] fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I', labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto desembolsado por mes') df2 = d['TIPO PRODUCTO'].value_counts().reset_index() df2.columns = ['TIPO PRODUCTO','CANT'] fig2 = px.pie(df2, names='TIPO PRODUCTO', values='CANT', title='Distribución por producto') fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas') df_col = d['SK_COLABORADOR'].value_counts().reset_index() df_col.columns = ['Colaborador','CANT'] fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores') fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)') df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index() df_seg.columns = ['Segmento','CANT'] fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento') m = folium.Map(location=[4.6, -74.1], zoom_start=6) mc = MarkerCluster().add_to(m) for ofi, coord in office_coords.items(): sub = d[d['OFICINA'].astype(str)==ofi] if sub.empty: continue total = sub['MONTO_I'].sum() folium.CircleMarker(location=coord, radius=6, fill=True, popup=f"{ofi}
Total: {total:,.0f} COP").add_to(mc) return fig1, fig2, fig3, fig4, fig5, fig6, m._repr_html_() # Interfaz Gradio with gr.Blocks() as demo: gr.Markdown("## Dashboard Bancamía – Análisis Exploratorio") with gr.Row(): with gr.Column(scale=1): f_inicio = gr.Textbox(label="Fecha inicio (YYYY-MM-DD)", value="2025-01-01") f_fin = gr.Textbox(label="Fecha fin (YYYY-MM-DD)", value="2025-03-31") zona = gr.Dropdown(zonas, label="Zona") tipos = gr.CheckboxGroup(choices=productos, label="Tipo de producto") colabor = gr.Dropdown(colaboradores, label="Colaborador", multiselect=True) segmento = gr.Dropdown(segmentos, label="Segmento", multiselect=True) btn = gr.Button("Actualizar") with gr.Column(scale=3): out1 = gr.Plot(); out2 = gr.Plot(); out3 = gr.Plot() out4 = gr.Plot(); out5 = gr.Plot(); out6 = gr.Plot() out7 = gr.HTML() btn.click(dashboard, [f_inicio, f_fin, zona, tipos, colabor, segmento], [out1, out2, out3, out4, out5, out6, out7]) if __name__ == "__main__": demo.launch()