hedtorresca commited on
Commit
0afac23
·
verified ·
1 Parent(s): c756b03

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +299 -290
app.py CHANGED
@@ -12,12 +12,12 @@ def load_offices_csv(path):
12
  sample = ''.join([next(f) for _ in range(10)])
13
  dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|'])
14
  sep = dialect.delimiter
15
- print(f"Delimitador detectado: '{sep}'")
16
- df_off = pd.read_csv(path, sep=sep, encoding='latin-1', engine='python', on_bad_lines='skip')
17
- df_off.columns = [c.strip() for c in df_off.columns]
18
- return df_off
19
 
20
- # 1. Cargar y preparar datos
21
  DATA_XLSX = "Base de Datos Prueba.xlsx"
22
  OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv"
23
 
@@ -26,315 +26,324 @@ df["MES"] = df["FECHA_APERTURA"].dt.to_period("M").dt.to_timestamp()
26
 
27
  off_meta = load_offices_csv(OFFICES_CSV)
28
 
29
- # Listas de filtros (sin departamento ni municipio)
30
- zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist())
31
- oficinas = sorted(off_meta['NOMBRE OFICINA'].dropna().astype(str).unique().tolist())
32
- productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist())
33
- colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist())
34
- segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist())
35
- min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max())
36
- min_plazo, max_plazo = int(df['PLAZO'].min()), int(df['PLAZO'].max())
 
37
 
38
- # Coordenadas hard-coded
39
  office_coords = {
40
- "Abrego": (8.080040, -73.219050),
41
- "Aguachica": (8.310229, -73.599837),
42
- "Aguazul": (5.171710, -72.547300),
43
- "Agustin Codazzi": (10.033471, -73.291284),
44
- "Andes": (5.655900, -75.879512),
45
- "Apartado": (7.885610, -76.634790),
46
- "Arauca": (7.086173, -70.757347),
47
- "Arjona": (10.297838, -75.308821),
48
- "Armenia": (6.167542, -75.764031),
49
- "Av. Caracas": (4.582934, -74.092877),
50
- "Ayapel": (0.000000, 0.000000),
51
- "Barrancabermeja": (7.059381, -73.862874),
52
- "Barrancas": (10.956670, -72.788870),
53
- "Barranquilla": (10.961041, -74.800959),
54
- "Belen": (5.989720, -72.913860),
55
- "Bello": (6.334930, -75.558280),
56
- "Bienestar Social Empleados": (0.000000, 0.000000),
57
- "Bosa": (5.930960, -73.616320),
58
- "Bosconia": (9.972841, -73.885721),
59
- "Bucaramanga Centro": (7.092868, -73.126498),
60
- "Buenaventura": (3.877616, -77.007365),
61
- "Buenos Aires": (3.013970, -76.646120),
62
- "Buga": (6.254484, -75.563634),
63
- "Cachingos": (0.000000, 0.000000),
64
- "Caldas": (6.089999, -75.636627),
65
- "Cali El Poblado (NO VIGENTE)": (3.418618, -76.497226),
66
- "Cali Norte": (6.300640, -70.205430),
67
- "Cali Sur": (0.000000, 0.000000),
68
- "Canal C": (0.000000, 0.000000),
69
- "Candelaria": (3.408325, -76.349086),
70
- "Carepa": (7.757548, -76.656274),
71
- "Carmen de Viboral": (6.082360, -75.335090),
72
- "Cartagena": (0.000000, 0.000000),
73
- "Cartagena Sur (NO VIGENTE)": (0.000000, 0.000000),
74
- "Cartago": (4.746743, -75.913598),
75
- "Castilla": (3.827220, -73.688310),
76
- "Caucasia": (7.987347, -75.196855),
77
- "Centro Bogota (NO VIGENTE)": (0.000000, 0.000000),
78
- "Centro MedelliAn": (6.254484, -75.563634),
79
- "Centro Suba": (5.828915, -73.035056),
80
- "Cerrito": (6.842993, -72.694730),
81
- "Chaparral": (3.750151, -75.340480),
82
- "Chia": (4.859712, -74.059663),
83
- "Chinacota": (7.607990, -72.600380),
84
- "Chinchina": (4.984375, -75.604801),
85
- "Chiquinquira": (5.618910, -73.819970),
86
- "Cienaga de Oro": (8.874380, -75.621750),
87
- "Cimitarra": (6.316110, -73.950540),
88
- "Copacabana": (6.348513, -75.507062),
89
- "Corozal": (9.317780, -75.295830),
90
- "Cucuta Atalaya": (7.907352, -72.524705),
91
- "Cucuta Centro": (7.884556, -72.504855),
92
- "Dabeiba": (7.033533, -76.167771),
93
- "Direccion General": (0.000000, 0.000000),
94
- "Duitama": (5.807690, -73.070165),
95
- "El Bagre": (7.596620, -74.804880),
96
- "El Banco": (8.998900, -73.970580),
97
- "El Cable (NO VIGENTE)": (0.000000, 0.000000),
98
- "El Tejar (NO VIGENTE)": (0.000000, 0.000000),
99
- "Engativa": (6.254484, -75.563634),
100
- "Envigado": (6.173196, -75.592097),
101
- "Espinal (NO VIGENTE)": (0.000000, 0.000000),
102
- "Facatativa": (4.811561, -74.384369),
103
- "Florencia": (1.682220, -77.072610),
104
- "Florida": (3.324420, -76.235460),
105
- "Floridablanca": (7.079171, -73.108311),
106
- "Fonseca": (10.888700, -72.851530),
107
- "Fontibon": (6.777790, -76.128580),
108
- "Fundacion": (10.521380, -74.186640),
109
- "Fundadores (NO VIGENTE)": (0.000000, 0.000000),
110
- "Funza": (4.714865, -74.212997),
111
- "Fusagasuga": (4.311530, -74.355406),
112
- "Galan": (6.637810, -73.288780),
113
- "Garagoa": (5.083373, -73.363727),
114
- "Garzon": (2.195783, -75.629006),
115
- "Gerencia Territorial - Sur": (0.000000, 0.000000),
116
- "Girardot": (4.303965, -74.804788),
117
- "Giron": (7.074196, -73.167534),
118
- "Granada": (4.519657, -74.353677),
119
- "Guaduas": (5.067815, -74.598816),
120
- "Ibague": (4.325569, -75.072920),
121
- "Ibague Centro (NO VIGENTE)": (0.000000, 0.000000),
122
- "Ipiales": (0.836103, -77.679298),
123
- "Itagui": (6.170260, -75.616540),
124
- "Jamundi": (3.111418, -76.606186),
125
- "Kennedy": (4.622480, -74.150010),
126
- "La America": (5.400098, -75.546666),
127
- "La Calera": (4.721216, -73.968126),
128
- "La Ceja": (6.032140, -75.431942),
129
- "La Dorada": (5.464481, -74.704063),
130
- "La Libertad": (2.445261, -76.632240),
131
- "La Plata": (2.391670, -75.891670),
132
- "La Union": (8.857282, -75.277048),
133
- "La Union - Valle": (4.537120, -76.104421),
134
- "La Victoria": (-0.111490, -71.110860),
135
- "Leticia": (-4.215596, -69.939065),
136
- "Libano": (4.922540, -75.063700),
137
- "Lorica (NO VIGENTE)": (0.000000, 0.000000),
138
- "Magangue": (9.186535, -74.788838),
139
- "Maicao": (11.350383, -72.352333),
140
- "Malaga": (6.702446, -72.731766),
141
- "Manizales": (5.050927, -75.519500),
142
- "Manrique": (6.484975, -75.019537),
143
- "Marinilla": (6.173840, -75.334800),
144
- "Mariquita": (5.198660, -74.896950),
145
- "Mesitas": (3.383561, -74.044270),
146
- "Minorista": (0.000000, 0.000000),
147
- "Mitu": (1.255250, -70.233390),
148
- "MoAitos": (0.000000, 0.000000),
149
- "Mocoa": (1.148930, -76.647750),
150
- "Mod Empoderados Costa Norte": (0.000000, 0.000000),
151
- "Mod Empoderados Plus-Bta": (0.000000, 0.000000),
152
- "Mod Empoderados Plus-M/llin": (0.000000, 0.000000),
153
- "Mod Empoderados Plus-Sur": (0.000000, 0.000000),
154
- "Molinos": (10.701780, -74.716750),
155
- "Mompox": (0.000000, 0.000000),
156
- "Moniquira": (5.877280, -73.570440),
157
- "MonteliAbano": (7.983010, -75.417260),
158
- "MonteriAa Centro": (8.754728, -75.881810),
159
- "Monteria": (8.773391, -75.817808),
160
- "Neiva (NO VIGENTE)": (0.000000, 0.000000),
161
- "Niquia": (2.649380, -75.636650),
162
- "OcaAa": (8.233420, -73.353310),
163
- "Orito": (0.665371, -76.872392),
164
- "PE Soledad Malambo": (10.861604, -74.773950),
165
- "PE Acacias": (3.987212, -73.765837),
166
- "PE Anserma": (5.230240, -75.787920),
167
- "PE Baranoa": (10.796676, -74.914419),
168
- "PE Belen": (5.989720, -72.913860),
169
- "PE Cali La Casona": (0.000000, 0.000000),
170
- "PE Cartagena del ChairA": (1.334860, -74.843460),
171
- "PE Dosquebradas": (4.839160, -75.667270),
172
- "PE Istmina": (0.000000, 0.000000),
173
- "PE La Pintada 2": (5.749960, -75.616299),
174
- "PE Majagual": (8.534730, -74.657180),
175
- "PE Pasto (NO VIGENTE)": (0.000000, 0.000000),
176
- "PE Puerto Wilches": (7.348801, -73.898273),
177
- "PE PurificaciA3n": (3.856779, -74.932103),
178
- "PE Santa Rosa del Sur": (7.946502, -74.026033),
179
- "PE TAoquerres": (0.000000, 0.000000),
180
- "PE Tame": (6.470290, -71.716970),
181
- "PE Valle del Guamuez": (0.452500, -76.919170),
182
- "PE Zarzal": (4.395820, -76.069830),
183
- "Pailitas": (8.957360, -73.623460),
184
- "Palmira": (3.538908, -76.298466),
185
- "Pamplona": (7.377155, -72.648957),
186
- "Parque de la CaAa (NO VIGENTE)": (0.000000, 0.000000),
187
- "Parque de las Luces (NO VIGENTE)": (0.000000, 0.000000),
188
- "Pasto": (0.000000, 0.000000),
189
- "Patio Bonito": (6.205447, -75.575411),
190
- "Paz de Ariporo": (5.881428, -71.891972),
191
- "Pedregal": (6.254484, -75.563634),
192
- "Perdomo": (2.888170, -75.433810),
193
- "Pereira": (4.812216, -75.692047),
194
- "Piedecuesta": (6.988034, -73.050030),
195
- "PiendamA3 (NO VIGENTE)": (0.000000, 0.000000),
196
- "Pitalito": (1.790464, -76.055636),
197
- "Pivijay": (10.447166, -74.408568),
198
- "Planeta Rica": (8.408920, -75.586800),
199
- "Plato": (9.791910, -74.782970),
200
- "Popayan Norte": (2.462388, -76.535919),
201
- "Popayan Sur": (2.462388, -76.535919),
202
- "Primero de Mayo": (5.821771, -73.043034),
203
- "Principal": (13.375420, -81.369090),
204
- "Puerto Asis": (0.497650, -76.497680),
205
- "Puerto Berrio": (6.490949, -74.402668),
206
- "Puerto Boyaca": (5.971557, -74.571408),
207
- "Puerto CarreAo": (6.190854, -67.484779),
208
- "Puerto IniArida": (-2.148770, -71.754990),
209
- "Puerto Libertador": (7.889560, -75.672370),
210
- "Punto Express Neiva 2": (0.000000, 0.000000),
211
- "Quibdo": (0.000000, 0.000000),
212
- "Quirigua": (2.649380, -75.636650),
213
- "Restrepo": (3.826566, -76.521106),
214
- "Riohacha": (11.381478, -72.905309),
215
- "Rionegro": (7.264871, -73.147840),
216
- "Riosucio": (5.421350, -75.703230),
217
- "Robledo (NO VIGENTE)": (0.000000, 0.000000),
218
- "Sahagun": (8.941133, -75.495272),
219
- "San AndrAs Isla": (6.809803, -72.849736),
220
- "San Andres de Sotavento": (9.144750, -75.508770),
221
- "San Bernardo": (4.178771, -74.421565),
222
- "San Fernando": (9.218060, -74.330294),
223
- "San Francisco (NO VIGENTE)": (0.000000, 0.000000),
224
- "San Gil": (6.557700, -73.133180),
225
- "San Jose del Guaviare": (2.562393, -72.640344),
226
- "San Juan": (4.461030, -73.680480),
227
- "San Marcos": (8.658430, -75.131200),
228
- "San Onofre": (9.737530, -75.525580),
229
- "San Pelayo (NO VIGENTE)": (0.000000, 0.000000),
230
- "San Vicente del Caguan": (2.113170, -74.769180),
231
- "San Vicente del Chucuri": (6.881050, -73.411570),
232
- "Santa Helenita": (10.325410, -74.961830),
233
- "Santa MariAa (NO VIGENTE)": (0.000000, 0.000000),
234
- "Santa Marta Av. Libertador": (11.231008, -74.175841),
235
- "Santa Rosa de Cabal": (4.890245, -75.626971),
236
- "Santafe de Antioquia": (6.556870, -75.828060),
237
- "Santander de Quilichao": (3.008790, -76.485900),
238
- "Santo Domingo": (6.472363, -75.164506),
239
- "Sincelejo": (9.300213, -75.395603),
240
- "Sincelejo Centro (NO VIGENTE)": (9.303609, -75.392834),
241
- "Soacha": (4.582123, -74.211534),
242
- "Sogamoso": (5.718314, -72.930984),
243
- "Soledad (NO VIGENTE)": (0.000000, 0.000000),
244
- "Suba": (5.451280, -73.814140),
245
- "Suba Rincon": (4.728370, -74.088350),
246
- "Tesoreria": (0.000000, 0.000000),
247
- "Tierralta": (8.173570, -76.059210),
248
- "ToberiAn": (0.000000, 0.000000),
249
- "Tulua": (4.084320, -76.196650),
250
- "Tumaco": (0.000000, 0.000000),
251
- "Tunja": (5.538590, -73.366380),
252
- "Turbo": (8.098040, -76.731690),
253
- "Ubate": (4.482420, -73.934840),
254
- "Urrao": (6.340116, -76.097593),
255
- "Valledupar": (10.469026, -73.257035),
256
- "Valledupar Centro": (10.476217, -73.245945),
257
- "Velez": (6.012930, -73.673140),
258
- "Venecia": (4.088080, -74.477460),
259
- "Villanueva": (4.609834, -72.927380),
260
- "Villavicencio": (4.144229, -73.634525),
261
- "Villeta": (5.011370, -74.471560),
262
- "Yarumal": (6.962765, -75.416779),
263
- "Yomasa": (4.519873, -74.092186),
264
- "Yopal": (5.340170, -72.394240),
265
- "Yumbo": (3.581378, -76.494648),
266
- "Zipaquira": (5.025810, -73.991283),
267
  }
268
 
269
- # Callback para filtrar oficinas según zona directamente (ya no hay dept/muni)
270
- def update_oficinas(zona):
 
 
 
 
 
 
 
271
  if zona:
272
- return sorted(off_meta[off_meta['ZONA']==zona]['NOMBRE OFICINA']
273
- .dropna().astype(str).unique().tolist())
274
- return oficinas
275
 
276
  # Dashboard
277
- def dashboard(zona, ofis, tipos_sel, colabor_sel, plazo_rango, segmento_sel, monto_rango):
 
278
  d = df.copy()
279
- if zona:
280
- d = d[d['ZONA']==zona]
 
 
281
  if ofis:
282
- d = d[d['OFICINA'].isin(ofis)]
283
- if tipos_sel:
284
- d = d[d['TIPO PRODUCTO'].isin(tipos_sel)]
285
- if colabor_sel:
286
- d = d[d['SK_COLABORADOR'].isin(colabor_sel)]
287
  if segmento_sel:
288
- d = d[d['SEGMENTO_CLIENTE'].isin(segmento_sel)]
289
- d = d[(d['PLAZO'] >= plazo_rango[0]) & (d['PLAZO'] <= plazo_rango[1])]
290
  d = d[(d['MONTO_I'] >= monto_rango[0]) & (d['MONTO_I'] <= monto_rango[1])]
 
291
 
292
  fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I',
293
- labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto por mes')
294
- fig2 = px.pie(d['TIPO PRODUCTO'].value_counts().reset_index().rename(columns={'index':'TIPO PRODUCTO',0:'CANT'}),
295
- names='TIPO PRODUCTO', values='CANT', title='Distribución por producto')
 
296
  fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas')
297
- df_col = d['SK_COLABORADOR'].value_counts().reset_index().rename(columns={'index':'Colaborador',0:'CANT'})
 
298
  fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores')
299
  fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)')
300
- df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index().rename(columns={'index':'Segmento',0:'CANT'})
 
301
  fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento')
302
 
303
  m = folium.Map(location=[4.6, -74.1], zoom_start=6)
304
  mc = MarkerCluster().add_to(m)
305
- for ofi in ofis or oficinas:
306
- coord = office_coords.get(ofi)
307
- if coord:
308
- total = d[d['OFICINA']==ofi]['MONTO_I'].sum()
309
- folium.CircleMarker(location=coord, radius=6, fill=True,
310
- popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc)
311
- map_html = m._repr_html_()
312
- return fig1, fig2, fig3, fig4, fig5, fig6, map_html
313
 
314
  # Interfaz Gradio
315
  with gr.Blocks() as demo:
316
- gr.Markdown('## Dashboard Bancamía – Filtros de Zona y Oficina')
317
  with gr.Row():
318
  with gr.Column(scale=1):
319
- zona = gr.Dropdown(zonas, label='Zona')
320
- ofis = gr.Dropdown(oficinas, label='Oficina', multiselect=True)
321
- tipos_sel = gr.CheckboxGroup(productos, label='Tipo de producto')
322
- colabor_sel = gr.Dropdown(colaboradores, label='Colaborador', multiselect=True)
323
- plazo_rango = gr.Slider(min_plazo, max_plazo, value=[min_plazo, max_plazo], label='Plazo (días)')
324
- segmento_sel = gr.Dropdown(segmentos, label='Segmento', multiselect=True)
325
- monto_rango = gr.Slider(min_amt, max_amt, value=[min_amt, max_amt], step=1000000, label='Monto (COP)')
326
- btn = gr.Button('Actualizar')
 
327
  with gr.Column(scale=3):
328
- out1 = gr.Plot()
329
- out2 = gr.Plot()
330
- out3 = gr.Plot()
331
- out4 = gr.Plot()
332
- out5 = gr.Plot()
333
- out6 = gr.Plot()
334
  out7 = gr.HTML()
335
- zona.change(update_oficinas, inputs=[zona], outputs=[ofis])
336
- btn.click(dashboard, inputs=[zona, ofis, tipos_sel, colabor_sel, plazo_rango, segmento_sel, monto_rango],
337
- outputs=[out1, out2, out3, out4, out5, out6, out7])
338
-
339
- if __name__ == '__main__':
340
- demo.launch()
 
12
  sample = ''.join([next(f) for _ in range(10)])
13
  dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|'])
14
  sep = dialect.delimiter
15
+ print(f"Delimitador detectado: '{sep}'".replace('{sep}', sep))
16
+ df = pd.read_csv(path, sep=sep, encoding='latin-1', engine='python', on_bad_lines='skip')
17
+ df.columns = [c.strip() for c in df.columns]
18
+ return df
19
 
20
+ # Carga y preparación de datos
21
  DATA_XLSX = "Base de Datos Prueba.xlsx"
22
  OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv"
23
 
 
26
 
27
  off_meta = load_offices_csv(OFFICES_CSV)
28
 
29
+ # Listas estáticas iniciales (convertir a str para evitar mezclas int/str)
30
+ departamentos = sorted(off_meta['DEPARTAMENTO'].dropna().astype(str).unique().tolist())
31
+ municipios = sorted(off_meta['CIUDAD'].dropna().astype(str).unique().tolist())
32
+ zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist())
33
+ productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist())
34
+ colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist())
35
+ segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist())
36
+ min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max())
37
+ min_plazo, max_plazo= int(df['PLAZO'].min()), int(df['PLAZO'].max())
38
 
39
+ # Coordenadas de oficinas
40
  office_coords = {
41
+ "Abrego": (8.080040, -73.219050),
42
+ "Aguachica": (8.310229, -73.599837),
43
+ "Aguazul": (5.171710, -72.547300),
44
+ "Agustin Codazzi": (10.033471, -73.291284),
45
+ "Andes": (5.655900, -75.879512),
46
+ "Apartado": (7.885610, -76.634790),
47
+ "Arauca": (7.086173, -70.757347),
48
+ "Arjona": (10.297838, -75.308821),
49
+ "Armenia": (6.167542, -75.764031),
50
+ "Av. Caracas": (4.582934, -74.092877),
51
+ "Ayapel": (0.000000, 0.000000),
52
+ "Barrancabermeja": (7.059381, -73.862874),
53
+ "Barrancas": (10.956670, -72.788870),
54
+ "Barranquilla": (10.961041, -74.800959),
55
+ "Belen": (5.989720, -72.913860),
56
+ "Bello": (6.334930, -75.558280),
57
+ "Bienestar Social Empleados": (0.000000, 0.000000),
58
+ "Bosa": (5.930960, -73.616320),
59
+ "Bosconia": (9.972841, -73.885721),
60
+ "Bucaramanga Centro": (7.092868, -73.126498),
61
+ "Buenaventura": (3.877616, -77.007365),
62
+ "Buenos Aires": (3.013970, -76.646120),
63
+ "Buga": (6.254484, -75.563634),
64
+ "Cachingos": (0.000000, 0.000000),
65
+ "Caldas": (6.089999, -75.636627),
66
+ "Cali El Poblado (NO VIGENTE)": (3.418618, -76.497226),
67
+ "Cali Norte": (6.300640, -70.205430),
68
+ "Cali Sur": (0.000000, 0.000000),
69
+ "Canal C": (0.000000, 0.000000),
70
+ "Candelaria": (3.408325, -76.349086),
71
+ "Carepa": (7.757548, -76.656274),
72
+ "Carmen de Viboral": (6.082360, -75.335090),
73
+ "Cartagena": (0.000000, 0.000000),
74
+ "Cartagena Sur (NO VIGENTE)": (0.000000, 0.000000),
75
+ "Cartago": (4.746743, -75.913598),
76
+ "Castilla": (3.827220, -73.688310),
77
+ "Caucasia": (7.987347, -75.196855),
78
+ "Centro Bogota (NO VIGENTE)": (0.000000, 0.000000),
79
+ "Centro MedelliAn": (6.254484, -75.563634),
80
+ "Centro Suba": (5.828915, -73.035056),
81
+ "Cerrito": (6.842993, -72.694730),
82
+ "Chaparral": (3.750151, -75.340480),
83
+ "Chia": (4.859712, -74.059663),
84
+ "Chinacota": (7.607990, -72.600380),
85
+ "Chinchina": (4.984375, -75.604801),
86
+ "Chiquinquira": (5.618910, -73.819970),
87
+ "Cienaga de Oro": (8.874380, -75.621750),
88
+ "Cimitarra": (6.316110, -73.950540),
89
+ "Copacabana": (6.348513, -75.507062),
90
+ "Corozal": (9.317780, -75.295830),
91
+ "Cucuta Atalaya": (7.907352, -72.524705),
92
+ "Cucuta Centro": (7.884556, -72.504855),
93
+ "Dabeiba": (7.033533, -76.167771),
94
+ "Direccion General": (0.000000, 0.000000),
95
+ "Duitama": (5.807690, -73.070165),
96
+ "El Bagre": (7.596620, -74.804880),
97
+ "El Banco": (8.998900, -73.970580),
98
+ "El Cable (NO VIGENTE)": (0.000000, 0.000000),
99
+ "El Tejar (NO VIGENTE)": (0.000000, 0.000000),
100
+ "Engativa": (6.254484, -75.563634),
101
+ "Envigado": (6.173196, -75.592097),
102
+ "Espinal (NO VIGENTE)": (0.000000, 0.000000),
103
+ "Facatativa": (4.811561, -74.384369),
104
+ "Florencia": (1.682220, -77.072610),
105
+ "Florida": (3.324420, -76.235460),
106
+ "Floridablanca": (7.079171, -73.108311),
107
+ "Fonseca": (10.888700, -72.851530),
108
+ "Fontibon": (6.777790, -76.128580),
109
+ "Fundacion": (10.521380, -74.186640),
110
+ "Fundadores (NO VIGENTE)": (0.000000, 0.000000),
111
+ "Funza": (4.714865, -74.212997),
112
+ "Fusagasuga": (4.311530, -74.355406),
113
+ "Galan": (6.637810, -73.288780),
114
+ "Garagoa": (5.083373, -73.363727),
115
+ "Garzon": (2.195783, -75.629006),
116
+ "Gerencia Territorial - Sur": (0.000000, 0.000000),
117
+ "Girardot": (4.303965, -74.804788),
118
+ "Giron": (7.074196, -73.167534),
119
+ "Granada": (4.519657, -74.353677),
120
+ "Guaduas": (5.067815, -74.598816),
121
+ "Ibague": (4.325569, -75.072920),
122
+ "Ibague Centro (NO VIGENTE)": (0.000000, 0.000000),
123
+ "Ipiales": (0.836103, -77.679298),
124
+ "Itagui": (6.170260, -75.616540),
125
+ "Jamundi": (3.111418, -76.606186),
126
+ "Kennedy": (4.622480, -74.150010),
127
+ "La America": (5.400098, -75.546666),
128
+ "La Calera": (4.721216, -73.968126),
129
+ "La Ceja": (6.032140, -75.431942),
130
+ "La Dorada": (5.464481, -74.704063),
131
+ "La Libertad": (2.445261, -76.632240),
132
+ "La Plata": (2.391670, -75.891670),
133
+ "La Union": (8.857282, -75.277048),
134
+ "La Union - Valle": (4.537120, -76.104421),
135
+ "La Victoria": (-0.111490, -71.110860),
136
+ "Leticia": (-4.215596, -69.939065),
137
+ "Libano": (4.922540, -75.063700),
138
+ "Lorica (NO VIGENTE)": (0.000000, 0.000000),
139
+ "Magangue": (9.186535, -74.788838),
140
+ "Maicao": (11.350383, -72.352333),
141
+ "Malaga": (6.702446, -72.731766),
142
+ "Manizales": (5.050927, -75.519500),
143
+ "Manrique": (6.484975, -75.019537),
144
+ "Marinilla": (6.173840, -75.334800),
145
+ "Mariquita": (5.198660, -74.896950),
146
+ "Mesitas": (3.383561, -74.044270),
147
+ "Minorista": (0.000000, 0.000000),
148
+ "Mitu": (1.255250, -70.233390),
149
+ "MoAitos": (0.000000, 0.000000),
150
+ "Mocoa": (1.148930, -76.647750),
151
+ "Mod Empoderados Costa Norte": (0.000000, 0.000000),
152
+ "Mod Empoderados Plus-Bta": (0.000000, 0.000000),
153
+ "Mod Empoderados Plus-M/llin": (0.000000, 0.000000),
154
+ "Mod Empoderados Plus-Sur": (0.000000, 0.000000),
155
+ "Molinos": (10.701780, -74.716750),
156
+ "Mompox": (0.000000, 0.000000),
157
+ "Moniquira": (5.877280, -73.570440),
158
+ "MonteliAbano": (7.983010, -75.417260),
159
+ "MonteriAa Centro": (8.754728, -75.881810),
160
+ "Monteria": (8.773391, -75.817808),
161
+ "Neiva (NO VIGENTE)": (0.000000, 0.000000),
162
+ "Niquia": (2.649380, -75.636650),
163
+ "OcaAa": (8.233420, -73.353310),
164
+ "Orito": (0.665371, -76.872392),
165
+ "PE Soledad Malambo": (10.861604, -74.773950),
166
+ "PE Acacias": (3.987212, -73.765837),
167
+ "PE Anserma": (5.230240, -75.787920),
168
+ "PE Baranoa": (10.796676, -74.914419),
169
+ "PE Belen": (5.989720, -72.913860),
170
+ "PE Cali La Casona": (0.000000, 0.000000),
171
+ "PE Cartagena del ChairA": (1.334860, -74.843460),
172
+ "PE Dosquebradas": (4.839160, -75.667270),
173
+ "PE Istmina": (0.000000, 0.000000),
174
+ "PE La Pintada 2": (5.749960, -75.616299),
175
+ "PE Majagual": (8.534730, -74.657180),
176
+ "PE Pasto (NO VIGENTE)": (0.000000, 0.000000),
177
+ "PE Puerto Wilches": (7.348801, -73.898273),
178
+ "PE PurificaciA3n": (3.856779, -74.932103),
179
+ "PE Santa Rosa del Sur": (7.946502, -74.026033),
180
+ "PE TAoquerres": (0.000000, 0.000000),
181
+ "PE Tame": (6.470290, -71.716970),
182
+ "PE Valle del Guamuez": (0.452500, -76.919170),
183
+ "PE Zarzal": (4.395820, -76.069830),
184
+ "Pailitas": (8.957360, -73.623460),
185
+ "Palmira": (3.538908, -76.298466),
186
+ "Pamplona": (7.377155, -72.648957),
187
+ "Parque de la CaAa (NO VIGENTE)": (0.000000, 0.000000),
188
+ "Parque de las Luces (NO VIGENTE)": (0.000000, 0.000000),
189
+ "Pasto": (0.000000, 0.000000),
190
+ "Patio Bonito": (6.205447, -75.575411),
191
+ "Paz de Ariporo": (5.881428, -71.891972),
192
+ "Pedregal": (6.254484, -75.563634),
193
+ "Perdomo": (2.888170, -75.433810),
194
+ "Pereira": (4.812216, -75.692047),
195
+ "Piedecuesta": (6.988034, -73.050030),
196
+ "PiendamA3 (NO VIGENTE)": (0.000000, 0.000000),
197
+ "Pitalito": (1.790464, -76.055636),
198
+ "Pivijay": (10.447166, -74.408568),
199
+ "Planeta Rica": (8.408920, -75.586800),
200
+ "Plato": (9.791910, -74.782970),
201
+ "Popayan Norte": (2.462388, -76.535919),
202
+ "Popayan Sur": (2.462388, -76.535919),
203
+ "Primero de Mayo": (5.821771, -73.043034),
204
+ "Principal": (13.375420, -81.369090),
205
+ "Puerto Asis": (0.497650, -76.497680),
206
+ "Puerto Berrio": (6.490949, -74.402668),
207
+ "Puerto Boyaca": (5.971557, -74.571408),
208
+ "Puerto CarreAo": (6.190854, -67.484779),
209
+ "Puerto IniArida": (-2.148770, -71.754990),
210
+ "Puerto Libertador": (7.889560, -75.672370),
211
+ "Punto Express Neiva 2": (0.000000, 0.000000),
212
+ "Quibdo": (0.000000, 0.000000),
213
+ "Quirigua": (2.649380, -75.636650),
214
+ "Restrepo": (3.826566, -76.521106),
215
+ "Riohacha": (11.381478, -72.905309),
216
+ "Rionegro": (7.264871, -73.147840),
217
+ "Riosucio": (5.421350, -75.703230),
218
+ "Robledo (NO VIGENTE)": (0.000000, 0.000000),
219
+ "Sahagun": (8.941133, -75.495272),
220
+ "San AndrAs Isla": (6.809803, -72.849736),
221
+ "San Andres de Sotavento": (9.144750, -75.508770),
222
+ "San Bernardo": (4.178771, -74.421565),
223
+ "San Fernando": (9.218060, -74.330294),
224
+ "San Francisco (NO VIGENTE)": (0.000000, 0.000000),
225
+ "San Gil": (6.557700, -73.133180),
226
+ "San Jose del Guaviare": (2.562393, -72.640344),
227
+ "San Juan": (4.461030, -73.680480),
228
+ "San Marcos": (8.658430, -75.131200),
229
+ "San Onofre": (9.737530, -75.525580),
230
+ "San Pelayo (NO VIGENTE)": (0.000000, 0.000000),
231
+ "San Vicente del Caguan": (2.113170, -74.769180),
232
+ "San Vicente del Chucuri": (6.881050, -73.411570),
233
+ "Santa Helenita": (10.325410, -74.961830),
234
+ "Santa MariAa (NO VIGENTE)": (0.000000, 0.000000),
235
+ "Santa Marta Av. Libertador": (11.231008, -74.175841),
236
+ "Santa Rosa de Cabal": (4.890245, -75.626971),
237
+ "Santafe de Antioquia": (6.556870, -75.828060),
238
+ "Santander de Quilichao": (3.008790, -76.485900),
239
+ "Santo Domingo": (6.472363, -75.164506),
240
+ "Sincelejo": (9.300213, -75.395603),
241
+ "Sincelejo Centro (NO VIGENTE)": (9.303609, -75.392834),
242
+ "Soacha": (4.582123, -74.211534),
243
+ "Sogamoso": (5.718314, -72.930984),
244
+ "Soledad (NO VIGENTE)": (0.000000, 0.000000),
245
+ "Suba": (5.451280, -73.814140),
246
+ "Suba Rincon": (4.728370, -74.088350),
247
+ "Tesoreria": (0.000000, 0.000000),
248
+ "Tierralta": (8.173570, -76.059210),
249
+ "ToberiAn": (0.000000, 0.000000),
250
+ "Tulua": (4.084320, -76.196650),
251
+ "Tumaco": (0.000000, 0.000000),
252
+ "Tunja": (5.538590, -73.366380),
253
+ "Turbo": (8.098040, -76.731690),
254
+ "Ubate": (4.482420, -73.934840),
255
+ "Urrao": (6.340116, -76.097593),
256
+ "Valledupar": (10.469026, -73.257035),
257
+ "Valledupar Centro": (10.476217, -73.245945),
258
+ "Velez": (6.012930, -73.673140),
259
+ "Venecia": (4.088080, -74.477460),
260
+ "Villanueva": (4.609834, -72.927380),
261
+ "Villavicencio": (4.144229, -73.634525),
262
+ "Villeta": (5.011370, -74.471560),
263
+ "Yarumal": (6.962765, -75.416779),
264
+ "Yomasa": (4.519873, -74.092186),
265
+ "Yopal": (5.340170, -72.394240),
266
+ "Yumbo": (3.581378, -76.494648),
267
+ "Zipaquira": (5.025810, -73.991283),
268
  }
269
 
270
+ # Callbacks
271
+ def update_municipios(dept):
272
+ return sorted(off_meta.dropna().astype(str).unique().tolist())
273
+
274
+ def update_zonas(dept, muni):
275
+ df2 = off_meta
276
+ return sorted(df2['ZONA'].dropna().astype(str).unique().tolist())
277
+
278
+ def update_oficinas(dept, muni, zona):
279
  if zona:
280
+ df2 = df2[df2['ZONA']==zona]
281
+ return sorted(df2['NOMBRE OFICINA'].dropna().astype(str).unique().tolist())
 
282
 
283
  # Dashboard
284
+ def dashboard(f_inicio, f_fin, zona, tipos, ofis,
285
+ monto_rango, colaborador_sel, plazo_rango, segmento_sel):
286
  d = df.copy()
287
+ if f_inicio:
288
+ d = d[d['FECHA_APERTURA'] >= pd.to_datetime(f_inicio)]
289
+ if f_fin:
290
+ d = d[d['FECHA_APERTURA'] <= pd.to_datetime(f_fin)]
291
  if ofis:
292
+ d = d[d['OFICINA'].astype(str).isin(ofis)]
293
+ if tipos:
294
+ d = d[d['TIPO PRODUCTO'].astype(str).isin(tipos)]
295
+ if colaborador_sel:
296
+ d = d[d['SK_COLABORADOR'].astype(str).isin(colaborador_sel)]
297
  if segmento_sel:
298
+ d = d[d['SEGMENTO_CLIENTE'].astype(str).isin(segmento_sel)]
 
299
  d = d[(d['MONTO_I'] >= monto_rango[0]) & (d['MONTO_I'] <= monto_rango[1])]
300
+ d = d[(d['PLAZO'] >= plazo_rango[0]) & (d['PLAZO'] <= plazo_rango[1])]
301
 
302
  fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I',
303
+ labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto desembolsado por mes')
304
+ df2 = d['TIPO PRODUCTO'].value_counts().reset_index()
305
+ df2.columns = ['TIPO PRODUCTO','CANT']
306
+ fig2 = px.pie(df2, names='TIPO PRODUCTO', values='CANT', title='Distribución por producto')
307
  fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas')
308
+ df_col = d['SK_COLABORADOR'].value_counts().reset_index()
309
+ df_col.columns = ['Colaborador','CANT']
310
  fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores')
311
  fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)')
312
+ df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index()
313
+ df_seg.columns = ['Segmento','CANT']
314
  fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento')
315
 
316
  m = folium.Map(location=[4.6, -74.1], zoom_start=6)
317
  mc = MarkerCluster().add_to(m)
318
+ for ofi, coord in office_coords.items():
319
+ sub = d[d['OFICINA'].astype(str)==ofi]
320
+ if sub.empty:
321
+ continue
322
+ total = sub['MONTO_I'].sum()
323
+ folium.CircleMarker(location=coord, radius=6, fill=True,
324
+ popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc)
325
+ return fig1, fig2, fig3, fig4, fig5, fig6, m._repr_html_()
326
 
327
  # Interfaz Gradio
328
  with gr.Blocks() as demo:
329
+ gr.Markdown("## Dashboard Bancamía – Análisis Avanzado")
330
  with gr.Row():
331
  with gr.Column(scale=1):
332
+ f_inicio = gr.Textbox(label="Fecha inicio (YYYY-MM-DD)", value="2025-01-01")
333
+ f_fin = gr.Textbox(label="Fecha fin (YYYY-MM-DD)", value="2025-03-31")
334
+ zona = gr.Dropdown(zonas, label="Zona")
335
+ tipos = gr.CheckboxGroup(choices=productos, label="Tipo de producto")
336
+ colabor = gr.Dropdown(colaboradores, label="Colaborador", multiselect=True)
337
+ plazo = gr.Slider(min_plazo, max_plazo, value=[min_plazo, max_plazo], label="Plazo (días)")
338
+ segmento = gr.Dropdown(segmentos, label="Segmento", multiselect=True)
339
+ monto = gr.Slider(min_amt, max_amt, value=[min_amt, max_amt], step=1000000, label="Monto (COP)")
340
+ btn = gr.Button("Actualizar")
341
  with gr.Column(scale=3):
342
+ out1 = gr.Plot(); out2 = gr.Plot(); out3 = gr.Plot()
343
+ out4 = gr.Plot(); out5 = gr.Plot(); out6 = gr.Plot()
 
 
 
 
344
  out7 = gr.HTML()
345
+ zona.change(update_oficinas, [zona], [ofis])
346
+ btn.click(dashboard, [f_inicio, f_fin, zona, tipos, ofis, monto, colabor, plazo, segmento],
347
+ [out1, out2, out3, out4, out5, out6, out7])
348
+ if __name__ == "__main__":
349
+ demo.launch()