Problem With DataFrame in Shiny

I am working in a Shiny app and I have a problem. Whenever it reads my Excel, processes my data using functions from a R file (utils. R, which works perfectly), and tries to show it in the Ver DataFrame tab, it shows every row, and instantly, they dissappear and I cannot explain why, or how to fix it.

observeEvent(input$btn_preprocesar_datos, {
  req(vals$df_original)
  cat("\n[DEBUG] ▶️ Iniciando preprocesado de datos...\n")

  res = prepara_df_ordenado(df = vals$df_original, fecha_inicio = as.Date("2024-01-15"))
  imputaciones_res = construir_imputaciones_B_dia_a_dia(
    df_ordenado = res$df_ordenado,
    min_prev = 3,
    incluir_rechazadas = FALSE
  )

  vals$df_B = imputaciones_res$imputaciones_B
  vals$df_original_preproc = res$df_ordenado

  cols_a_texto = c("Mes", "Puesto", "Área", "Terna", "Operario")
  for (col in cols_a_texto) {
    if (col %in% names(vals$df_B)) vals$df_B[[col]] <- as.character(vals$df_B[[col]])
  }

  df_use = vals$df_B
  meses   = if ("Mes" %in% names(df_use)) sort(unique(na.omit(as.character(df_use$Mes)))) else character(0)
  puestos = if ("Puesto" %in% names(df_use)) sort(unique(na.omit(as.character(df_use$Puesto)))) else character(0)
  areas   = if ("Área" %in% names(df_use)) sort(unique(na.omit(as.character(df_use$Área)))) else character(0)

  updateSelectInput(session, "mes_df", choices = c("-", as.character(meses)), selected = "-")
  updateSelectInput(session, "puesto_df", choices = c("-", as.character(puestos)), selected = "-")
  updateSelectInput(session, "area_df", choices = c("-", as.character(areas)), selected = "-")

  cat("[DEBUG] Filtros actualizados: mes_df =", input$mes_df, ", puesto_df =", input$puesto_df, "\n")
})

# --- Filtro del DataFrame mostrado en la pestaña "Ver DataFrame" ---
df_filtrado_df = reactive({
  f <- if (input$df_choice == "df_B") vals$df_B else vals$df_original_preproc %||% vals$df_original
  req(f)

  valido <- function(x) !is.null(x) && nzchar(x) && x != "-"

  if (valido(input$mes_df)     && "Mes"      %in% names(f)) f <- f[f$Mes      == input$mes_df, ]
  if (valido(input$puesto_df)  && "Puesto"   %in% names(f)) f <- f[f$Puesto   == input$puesto_df, ]
  if (valido(input$area_df)    && "Área"     %in% names(f)) f <- f[f$Área     == input$area_df, ]
  if (valido(input$terna_df)   && "Terna"    %in% names(f)) f <- f[f$Terna    == input$terna_df, ]
  if (valido(input$operario_df)&& "Operario" %in% names(f)) f <- f[f$Operario == input$operario_df, ]

  cat("[DEBUG] Filtrado →", nrow(f), "filas después del filtrado (mes_df =", input$mes_df, ")\n")
  f
})

output$tabla_df = renderDT({
  req(df_filtrado_df())
  f <- df_filtrado_df()
  datatable(f, options = list(pageLength = 10, scrollX = TRUE))
})

It's difficult for anyone to help without any code to run. Can you post a minimal reproducible example of a shiny app demonstrating the problem? There are some tips here: https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example

Ow, I a, sorry man, it's my first time in communities like these, and I'm not used to it. Sorry.


library(shiny)
library(readxl)
library(readr)
library(DT)
library(tools)

ui <- fluidPage(
  titlePanel("MRE - Asignación de Áreas según última línea"),
  
  sidebarLayout(
    sidebarPanel(
      fileInput("archivo", "Sube un archivo CSV o Excel",
                accept = c(".csv", ".xlsx", ".xls")),
      actionButton("procesar", "Procesar archivo")
    ),
    
    mainPanel(
      h4("Resultado del DataFrame"),
      DTOutput("tabla"),
      verbatimTextOutput("debug")
    )
  )
)

server <- function(input, output, session) {
  vals <- reactiveValues(df = NULL, areas_puestos = list(), debug_msg = "")
  
  observeEvent(input$procesar, {
    req(input$archivo)
    ruta <- input$archivo$datapath
    ext  <- tolower(file_ext(input$archivo$name))
    
    # --- Lectura robusta simplificada ---
    if (ext == "csv") {
      lineas <- readLines(ruta, encoding = "UTF-8")
      ultima <- trimws(tail(lineas, 1))
      lineas <- head(lineas, -1)
      sep <- if (grepl(";", lineas[1])) ";" else ","
      df <- read_delim(paste(lineas, collapse = "\n"), delim = sep, show_col_types = FALSE)
      linea_area <- ultima
    } else if (ext %in% c("xlsx", "xls")) {
      df <- suppressWarnings(read_excel(ruta, col_types = "text"))
      ultima_fila <- df[nrow(df), ]
      valores <- as.character(unlist(ultima_fila))
      valores <- valores[!is.na(valores) & valores != ""]
      linea_area <- paste(valores, collapse = " ")
      df <- df[-nrow(df), ]
    } else {
      showNotification("Formato no soportado", type = "error")
      return(NULL)
    }
    
    # --- Parsear áreas ---
    vals$debug_msg <- paste0("Línea detectada: ", linea_area, "\n")
    areas_raw <- unlist(strsplit(linea_area, "[,;]"))
    areas_raw <- trimws(areas_raw[areas_raw != ""])
    for (a in areas_raw) {
      partes <- unlist(strsplit(a, ":", fixed = TRUE))
      if (length(partes) >= 2) {
        area <- partes[1]
        puestos <- unlist(strsplit(partes[2], "[,;]"))
        vals$areas_puestos[[area]] <- puestos
      }
    }
    
    # --- Añadir columna Área según Puesto ---
    if ("Puesto" %in% names(df) && length(vals$areas_puestos) > 0) {
      df$Área <- NA_character_
      for (area in names(vals$areas_puestos)) {
        puestos_area <- vals$areas_puestos[[area]]
        df$Área[df$Puesto %in% puestos_area] <- area
      }
    }
    
    vals$df <- df
    vals$debug_msg <- paste0(vals$debug_msg, "Áreas detectadas:\n", 
                             paste(capture.output(str(vals$areas_puestos)), collapse = "\n"))
  })
  
  output$tabla <- renderDT({
    req(vals$df)
    datatable(vals$df, options = list(pageLength = 5))
  })
  
  output$debug <- renderText({
    vals$debug_msg
  })
}

shinyApp(ui, server)

Puesto,Valor
1,10
2,20
3,30
1:1,2:2;3

Use this as an example CSV (or feel free to adjust in anyway)

Does this example really fail in the same way as your actual app?

Reactive flows seem completely different. Table output in your first post comes from df_filtrado_df() reactive, which in turn has quite a few dependencies and it's easy to accidentally invalidate it. In your last example there's vals$dfthat is only updated through observeEvent() and is granted to hold its value until user clicks on action button.

@gala3573 No problem - just trying to explain what's needed to be able to help. Often just making a minimal example can help you fix it yourself too.

When I run that I get this, but no errors:

Not sure if this is helpful, but you may be over complicating the reactivity - do you need for the processing of the file to be a separate event to the upload? You can just use something like this to process the file whenever it is uploaded:

df <- reactive({
  req(input$archivo)
  <do stuff with input$archivo>
})

output$table <- renderTable(df())

@Simon_Smart
Thanks a lot for the suggestion :raising_hands:
In my case, the app involves a heavier workflow — the uploaded file needs to go through several preprocessing steps (some custom R functions that take a few seconds (even minutes), create new dataframes, and update multiple dynamic inputs).
If I handled all that inside a single reactive({ ... }) tied directly to input$archivo, Shiny would re-run the whole preprocessing each time any dependent input changes, which isn’t ideal for performance or user control.
That’s why I trigger the processing explicitly with an observeEvent(input$btn_preprocesar) — it gives me more control over when the heavy work happens.
But you’re absolutely right that for simpler apps, where you just need to read and display the file, your approach is a much cleaner and more reactive solution. Thanks again!

@margusl
You’re absolutely right — this minimal example doesn’t replicate the exact reactive flow from my full app.
In the real app, the table output depends on a reactive (df_filtrado_df()) that’s tied to several user inputs (month, area, operator, etc.), so it can easily be invalidated when any of those change.
In the MRE, vals$df only updates inside an observeEvent(input$procesar), which keeps the data stable until the user clicks the button again.
I mainly built the MRE to illustrate the area/puesto parsing part in isolation, but not the complex filtering reactivity.
You’re right that the two reactive flows behave differently — that’s probably why the simplified version doesn’t reproduce the disappearing-table issue. Thanks for catching that!

You are just proxying this thread to an LLM, aren't you?

If that app is also LLM generated, it might be easier to just start over. Let it lay out a plan first and ask it to include automated tests and/or manual verification checks that must be passed after each stage and let it move from one stage to another only when all previous tests pass. If it tends to stick with older patterns, perhaps try feeding it a current Shiny manual and package news through system prompt.

If you are certain that reactivity in current app is wired almost correctly and can be fixed without a complete re-write, then maybe start with a clean server() function and try to move back blocks one at a time to check if / what / how is actually working. If all your observers and reactives include the same level of logging as in your original post, it might not be too hard to track reactive flow from console messages.