# --- Para la manipulación de datos --- import pandas as pd from datetime import datetime from share_com import tips, regist, app_dir # Importa tus DataFrames import faicons as fa import plotly.express as px from shiny.ui import page_navbar from shiny import reactive, render from shiny.express import input, ui from shinywidgets import render_plotly import share_com from shiny import express as sx from io import BytesIO # --- Para gráficos e iconos --- import share_com print(share_com.tips.head()) bill_rng = tips['assocName'].unique() asocc = regist['parentOrganization'].unique() generos_unicos = regist['gender'].unique().tolist()[0:2] discipline = regist['discipline'].unique().tolist() discipline_compe = tips['discipline'].unique().tolist() active_tips = tips['status'].unique().tolist() value = [0,1,3] discipline = [discipline[i] for i in value] tips.info() # Add page title and sidebar ui.page_opts( title="App with Data", ) with ui.nav_panel("Competiciones"): with ui.layout_sidebar(): with ui.sidebar(open="desktop"): ui.img(src="FVF.png", width="250px") ui.input_select( "tourment", "Selecciona una Opción Asociaciones", ["TODAS"] + list(bill_rng) , selected=["FVF"] ) ui.input_checkbox_group( "active", # Un ID diferente "Estatus", choices=active_tips, selected=active_tips, # Por defecto, todas están seleccionadas inline=True ) ui.input_checkbox_group( "disci_comp", # Un ID diferente "Disciplina", choices=discipline_compe, selected=discipline_compe, # Por defecto, todas están seleccionadas inline=True ) ui.input_dark_mode() ICONS = { "user": fa.icon_svg("user", "regular"), "wallet": fa.icon_svg("wallet"), "currency-dollar": fa.icon_svg("dollar-sign"), "ellipsis": fa.icon_svg("ellipsis"), "gavel" : fa.icon_svg("gavel"), "clipboard-list" : fa.icon_svg("clipboard-list") } with ui.layout_columns(fill=False): with ui.value_box(showcase=ICONS["user"]): "Total Competiciones" @render.express def total_competiciones(): # CORREGIDO: Nombre de variable simplificado seleccion = input.tourment() disciplina = input.disci_comp() status = input.active() d = tips.copy() if seleccion != "TODAS": d = d[d['assocName'] == seleccion ] if status: # Nos aseguramos de que la lista de géneros no esté vacía d = d[d['status'].isin(status)] if disciplina: d = d[d['discipline'].isin(disciplina)] number = len(d['competitionTypeId'].unique()) number with ui.value_box(showcase = ICONS['clipboard-list']): "Cantidad de Asociaciones" @render.express def total_asociaciones(): # CORREGIDO: Nombre de variable simplificado seleccion = input.tourment() disciplina = input.disci_comp() status = input.active() d = tips.copy() if seleccion != "TODAS": d = d[d['assocName'] == seleccion] if seleccion != "TODAS": d = d[d['assocName'] == seleccion ] if status: # Nos aseguramos de que la lista de géneros no esté vacía d = d[d['status'].isin(status)] if disciplina: d = d[d['discipline'].isin(disciplina)] number = len(d['assocId'].unique()) number with ui.layout_columns(col_widths=[12,6,6]): with ui.card(full_screen=True): with ui.card_header(class_="d-flex justify-content-between align-items-center"): "Cantidad de Competiciones por Asociación" @render_plotly def col_plot(): seleccion = input.tourment() disciplina = input.disci_comp() status = input.active() d = tips.copy() if seleccion != "TODAS": d = d[d['assocName'] == seleccion ] if status: # Nos aseguramos de que la lista de géneros no esté vacía d = d[d['status'].isin(status)] if disciplina: d = d[d['discipline'].isin(disciplina)] conteo_asociaciones = d['assocName'].value_counts().reset_index() # Renombramos las columnas para mayor claridad en el gráfico conteo_asociaciones.columns = ['Asociacion', 'Cantidad de Competiciones'] # Ordenamos los datos para que el gráfico se vea más limpio (de mayor a menor) conteo_asociaciones = conteo_asociaciones.sort_values( by='Cantidad de Competiciones', ascending=False ) fig = px.bar( conteo_asociaciones, x='Cantidad de Competiciones', y='Asociacion', title='Número de Competiciones por Asociación', text_auto=True, # Pone los números en las barras template='plotly_white', # Un tema limpio y profesional color='Cantidad de Competiciones', # Colorea las barras según la cantidad color_continuous_scale= px.colors.sequential.Blues ) fig.update_layout(title_x=0.5) # Centra el título fig.update_xaxes(visible=False) # Oculta el eje X (es redundante) fig.update_yaxes(title=None, automargin=True) fig.update_xaxes(tickangle=45) return fig with ui.nav_panel("Registro"): with ui.layout_sidebar(): with ui.sidebar(open="desktop"): ui.img(src="FVF.png", width="250px") ui.input_select( "select", "Selecciona una Opción Asociaciones", ["TODAS"] + list(asocc) , selected=["TODAS"] ) ui.input_checkbox_group( "genero_multi", # Un ID diferente "Selecciona Género(s)", choices=generos_unicos, selected=generos_unicos, # Por defecto, todas están seleccionadas inline=True ) ui.input_checkbox_group( "disci", # Un ID diferente "Disciplina", choices=discipline, selected=discipline, # Por defecto, todas están seleccionadas inline=True ) ui.input_dark_mode() ICONS = { "user": fa.icon_svg("user", "regular"), "wallet": fa.icon_svg("wallet"), "currency-dollar": fa.icon_svg("dollar-sign"), "ellipsis": fa.icon_svg("ellipsis"), "gavel" : fa.icon_svg("gavel"), "clipboard-list" : fa.icon_svg("clipboard-list") } with ui.layout_columns(fill=False): with ui.value_box(showcase=ICONS["user"]): "Total Jugadores" @render.express def total_registro_(): # CORREGIDO: Nombre de variable simplificado seleccion = input.select() genre = input.genero_multi() disci = input.disci() c = regist.copy() if seleccion != "TODAS": c = c[c['parentOrganization'] == seleccion] # 2. CORRECCIÓN: Filtramos por género usando .isin() if genre: # Nos aseguramos de que la lista de géneros no esté vacía c = c[c['gender'].isin(genre)] if disci: c = c[c['discipline'].isin(disci)] # 3. Filtramos por categoría "Jugador" y status "ACTIVO" jugadores_filtrados = c[ (c['registrationCategory'] == "Jugador") & (c['status'] == "ACTIVO") ] # Elimina duplicados de personas y cuenta el resultado count = jugadores_filtrados['personId'].nunique() count with ui.value_box(showcase=ICONS["gavel"]): "Total de Árbitros" @render.express def total_registro_ar(): # CORREGIDO: Nombre de variable simplificado seleccion = input.select() c = regist.copy() def contar_registros_activos(df, categoria): # Filtra por categoría y status en un solo paso filtrado = df[ (df['registrationCategory'] == categoria) & (df['status'] == "ACTIVO") ] # Elimina duplicados de personas y cuenta el resultado cantidad = filtrado.drop_duplicates(subset='personId')['personId'].nunique() return cantidad if seleccion != "TODAS": c = c[c['parentOrganization'] == seleccion] arbitro = contar_registros_activos(c, "Árbitro") arbitro with ui.value_box(showcase=ICONS["clipboard-list"]): "Total de Entrenadores" @render.express def total_registro_entre(): seleccion = input.select() seleccion_disc = input.disci() seleccion_genero = input.genero_multi() c = regist.copy() if seleccion != "TODAS": c = c[c['parentOrganization'] == seleccion ] if seleccion_genero: # Nos aseguramos de que la lista de géneros no esté vacía c = c[c['gender'].isin(seleccion_genero)] if seleccion_disc: c = c[c['discipline'].isin(seleccion_disc)] def contar_registros_activos(df, categoria): # Filtra por categoría y status en un solo paso filtrado = df[ (df['registrationCategory'] == categoria) & (df['status'] == "ACTIVO") ] # Elimina duplicados de personas y cuenta el resultado cantidad = filtrado.drop_duplicates(subset='personId')['personId'].nunique() return cantidad if seleccion != "TODAS": c = c[c['parentOrganization'] == seleccion] entre = contar_registros_activos(c, "Entrenador") entre with ui.value_box(showcase=ICONS["user"]): "Edad Promedio" @render.express def edad_promedio(): # CORREGIDO: Nombre de variable simplificado seleccion = input.select() seleccion_disc = input.disci() seleccion_genero = input.genero_multi() c = regist.copy() if seleccion != "TODAS": c = c[c['parentOrganization'] == seleccion ] if seleccion_genero: # Nos aseguramos de que la lista de géneros no esté vacía c = c[c['gender'].isin(seleccion_genero)] if seleccion_disc: c = c[c['discipline'].isin(seleccion_disc)] edad_df = c.drop_duplicates(subset=['personId']) edad_df = edad_df[edad_df['status'] == 'ACTIVO'] edad_df = edad_df.dropna(subset=['dateOfBirth']) edad_df['dateOfBirth'] = pd.to_datetime(edad_df['dateOfBirth']) hoy = datetime.now() edad_df['edad'] = (hoy - edad_df['dateOfBirth']).dt.days / 365.25 edad_df['edad'] = edad_df['edad'].astype(int) edad_promedio = edad_df['edad'].mean() round(edad_promedio, 2) with ui.layout_columns(col_widths=[12, 6, 6]): with ui.card(full_screen=True): with ui.card_header("Datos Evaluados"): @render.data_frame def tabla_registros(): # IMPORTANTE: @render.data_frame SÍ necesita un 'return'. return render.DataGrid(datos_filtrados()[['personId', 'firstName', 'lastName','orgName', 'status','registrationCategory', 'parentOrganization','discipline']].head()) @reactive.calc def datos_filtrados(): seleccion_disc = input.disci() seleccion_asoc = input.select() seleccion_genero = input.genero_multi() c = regist.copy() if seleccion_asoc!= "TODAS": c = c[c['parentOrganization'] == seleccion_asoc] # 2. CORRECCIÓN: Filtramos por género usando .isin() if seleccion_genero: # Nos aseguramos de que la lista de géneros no esté vacía c = c[c['gender'].isin(seleccion_genero)] if seleccion_disc: c = c[c['discipline'].isin(seleccion_disc)] jugadores_filtrados = c[c['status'] == "ACTIVO"] return jugadores_filtrados