You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

435 lines
20 KiB

  1. library(shiny)
  2. library(openCyto)
  3. library(flowCore)
  4. library(flowWorkspace)
  5. library(CytoML)
  6. library(ggcyto)
  7. # library(reshape2)
  8. # library(CitFuns)
  9. library(openxlsx)
  10. library(tidyverse)
  11. # Define UI for application that draws a histogram
  12. ui <- fluidPage(
  13. # Application title
  14. titlePanel("Análisis Citometría ImmunoPreserve"),
  15. # Sidebar with a slider input for number of bins
  16. sidebarLayout(
  17. sidebarPanel(
  18. selectInput("phenotype", "Panel", selected="Panel1", choices=c("Panel1", "Panel2","Panel3","panel4")),
  19. ),
  20. mainPanel(
  21. textInput("cytopath", label="Directorio fenotipo", value=""),
  22. actionButton("goButtonDir","Selecciona directorio fenotipo"),
  23. textOutput("session"),
  24. hr(),
  25. actionButton("fcsconvert", "Convertir a fcs"),
  26. hr(),
  27. actionButton("pngexport", "Exportar informes"),
  28. hr(),
  29. textInput("dbpath", label="Ruta Base de Dades", value=""),
  30. actionButton("goButtondbpath","Selecciona Ruta Base de Dades"),
  31. textOutput("sessiondbpath"),
  32. actionButton("popexport", "Actualizar BBDD")
  33. )
  34. )
  35. )
  36. # Define server logic required to draw a histogram
  37. server <- function(input, output) {
  38. observe({
  39. if(input$goButtonDir > 0){
  40. if (input$cytopath == ""){
  41. cito_dir<<-choose.dir() %>% gsub("\\","/",. ,fixed=T) %>% paste0("/") %>% stringi::stri_enc_tonative()
  42. }else{
  43. cito_dir<<-input$cytopath %>% gsub("\\","/",. ,fixed=T) %>% gsub("/$", "", .) %>% paste0("/") %>% stringi::stri_enc_tonative()
  44. }
  45. output$session <- renderText(
  46. cito_dir
  47. )
  48. }
  49. })
  50. observe({
  51. if(input$goButtondbpath > 0){
  52. if (input$dbpath == ""){
  53. db_path<<-choose.dir() %>% gsub("\\","/",. ,fixed=T) %>% paste0("/") %>% stringi::stri_enc_tonative()
  54. }else{
  55. db_path<<-input$dbpath %>% gsub("\\","/",. ,fixed=T) %>% gsub("/$", "", .) %>% paste0("/") %>% stringi::stri_enc_tonative()
  56. }
  57. output$sessiondbpath <- renderText(
  58. db_path
  59. )
  60. }
  61. })
  62. observeEvent(input$fcsconvert,{
  63. route<-cito_dir
  64. files<-list.files(route, ".LMD")
  65. for (lmd in files){
  66. fcs<-read.FCS(paste0(route,lmd), dataset = 2)
  67. keyword(fcs)['$FIL']<-paste0(gsub(".LMD","",lmd), ".fcs")
  68. write.FCS(fcs, paste0(route, gsub(".LMD","",lmd), ".fcs"))
  69. }
  70. print("Conversión completada")
  71. })
  72. observeEvent(input$pngexport,{
  73. # if (input$phenotype == "Pop"){
  74. # route<-cito_dir
  75. #
  76. # ws<-open_flowjo_xml(paste0(route,"Populations.wsp"))
  77. # gs<-flowjo_to_gatingset(ws, name="All Samples")
  78. #
  79. # sampleNames(gs)<-sapply(sampleNames(gs), function(x) strsplit(x, "Pop ")[[1]][2]) %>%
  80. # gsub("[[:space:]][0-9]*.fcs_.[0-9]*","", . , perl = T)
  81. #
  82. # for (samp in sampleNames(gs)){
  83. # print(samp)
  84. # p<-autoplot(gs[[samp]], bins=64)
  85. # ggsave(paste0(route, samp,".pop.png"),p,width = 10, height = 10)
  86. # }
  87. # }
  88. if (input$phenotype == "Panel1"){
  89. route<-cito_dir
  90. ws<-open_flowjo_xml(paste0(route,"Panel1.wsp"))
  91. gs<-flowjo_to_gatingset(ws, name="All Samples")
  92. sampleNames(gs)<-sapply(sampleNames(gs), function(x) strsplit(x, "Panel1 ")[[1]][2]) %>%
  93. gsub("[[:space:]][0-9]*.fcs_.[0-9]*","", . , perl = T)
  94. gs<-gs[sampleNames(gs)[!grepl("Iso|ISO|iso",sampleNames(gs))]]
  95. bool.comb<-apply(
  96. expand.grid(c("","!"), c("","!"), c("","!"), c("","!")),
  97. 1,
  98. function(x) paste0(x[1],"CTLA4 & ",x[2],"LAG3 & ",x[3],"PD1 & ",x[4], "TIM3")
  99. )
  100. bool.name<-apply(
  101. expand.grid(c("+","-"), c("+","-"), c("+","-"), c("+","-")),
  102. 1,
  103. function(x) paste0("CTLA4",x[1]," LAG3",x[2]," PD1",x[3]," TIM3",x[4])
  104. )
  105. print("Booleanos CD8")
  106. for (i in 1:length(bool.comb)){
  107. call<-substitute(booleanFilter(v), list(v=as.symbol(bool.comb[i])))
  108. boolgate<-eval(call)
  109. gs_pop_add(gs, boolgate, parent="_CD8", name = bool.name[i])
  110. }
  111. print("Booleanos CD4")
  112. for (i in 1:length(bool.comb)){
  113. call<-substitute(booleanFilter(v), list(v=as.symbol(bool.comb[i])))
  114. boolgate<-eval(call)
  115. gs_pop_add(gs, boolgate, parent="_CD4", name = bool.name[i])
  116. }
  117. recompute(gs)
  118. names<-sampleNames(gs) # %>% gsub("ab|Ab|AB|iso|Iso|ISO| ","",.) %>% unique()
  119. nodes<-gs_get_pop_paths(gs)
  120. # nodes<-gsub("â\u0081»", "-", nodes)
  121. # nodes<-gsub("â\u0081º", "+", nodes)
  122. nodes<-nodes[grepl("CTLA4", nodes)]
  123. nodes<-nodes[!grepl("_CD4$|_CD8$|CTLA4$|TIM3$|PD1$|LAG3$", nodes)]
  124. pop<-gs_pop_get_stats(gs, nodes=nodes,type="percent") %>% as.data.frame %>% mutate(percent=percent*100)
  125. pop$percent<-round(pop$percent, digits=2)
  126. # pop$pop<-gsub("â\u0081»", "n", pop$pop)
  127. # pop$pop<-gsub("â\u0081º", "p", pop$pop)
  128. pop$pop<-gsub("-", "n", pop$pop, fixed=T)
  129. pop$pop<-gsub("+", "p", pop$pop, fixed=T)
  130. pop$pop<-gsub(" ", "_", pop$pop)
  131. ## Esto si no hay isotipo
  132. pop_sp<-pop
  133. pop_sp["Population"]<-str_extract(pop_sp$pop, "/_CD[4,8]{1}/") %>% gsub("/|_","",.)
  134. pop_sp$pop<-sapply(strsplit(pop_sp$pop, "/"), tail, 1)
  135. pop_sp<-pop_sp %>% spread(pop, percent)
  136. ## Esto si hay Isotipo
  137. # pop["Type"]<-"ab"
  138. # pop[grepl("iso|ISO|Iso",pop$sample),"Type"]<-"iso"
  139. # pop$sample<-gsub("iso|ISO|Iso|ab|AB|Ab| ","",pop$sample)
  140. # pop_sp<-pop %>% spread(Type, percent)
  141. # pop_sp["Net"]<-pop_sp$ab
  142. # pop_sp[!grepl("CTLA4n_LAG3n_PD1n_TIGITn_TIM3n",pop_sp$pop),"Net"]<-pop_sp[!grepl("CTLA4n_LAG3n_PD1n_TIGITn_TIM3n",pop_sp$pop),"ab"]-pop_sp[!grepl("CTLA4n_LAG3n_PD1n_TIGITn_TIM3n",pop_sp$pop),"iso"]
  143. # pop_sp$Net[pop_sp$Net < 0]<-0
  144. # pop_sp["Population"]<-str_extract(pop_sp$pop, "/CD[4,8]{1}/") %>% gsub("/","",.)
  145. # pop_sp$pop<-sapply(strsplit(pop_sp$pop, "/"), tail, 1)
  146. #
  147. # pop_sp<-pop_sp %>% select(-ab,-iso) %>% spread(pop,Net)
  148. # pop_sp$CTLA4n_LAG3n_PD1n_TIGITn_TIM3n<- pop_sp %>% select(-CTLA4n_LAG3n_PD1n_TIGITn_TIM3n) %>% group_by(sample,Population) %>%
  149. # gather(pop, value, -sample,-Population) %>% summarise(n=100-sum(value)) %>% pull(n)
  150. # if (input$dbtype == "OV"){
  151. # pop_sp <- rename(pop_sp, "samples"="sample")
  152. # }
  153. # if (input$dbtype %in% c("UM", "CC")){
  154. # pop_sp <- rename(pop_sp, "CODIGO"="sample")
  155. #
  156. pop_sql<-read.xlsx(paste0(db_path,"Panel1.xlsx"), sheet = "IC")
  157. pop_sp<-pop_sp %>% merge(pop_sql %>% slice(0), all=T) %>% select(colnames(pop_sql))
  158. for (id in names){
  159. print(id)
  160. # iso<-sampleNames(gs)[grepl(id, sampleNames(gs)) & grepl("iso|Iso|ISO",sampleNames(gs))]
  161. # ab<-sampleNames(gs)[grepl(id, sampleNames(gs)) & grepl("ab|Ab|AB",sampleNames(gs))]
  162. data<-pop_sp %>% filter(sample == id)
  163. data1<-data %>% gather(phen, value, -sample, -Population)
  164. data1$phen<-gsub("p","+",data1$phen)
  165. data1$phen<-gsub("n","-",data1$phen)
  166. data1$phen<-gsub("_"," ",data1$phen)
  167. data1$phen<-gsub("n","-",data1$phen, fixed = T)
  168. data1$phen<-gsub("p","+",data1$phen, fixed = T)
  169. data1$phen<-gsub("_"," ",data1$phen)
  170. data1[data1$value < 1, "phen"]<-"Other"
  171. data1$phen<-gsub("[A-Z]*-*[0-9T]- *", "", data1$phen)
  172. data1$phen<-gsub("+ $", "", data1$phen)
  173. data1$phen[data1$phen == ""]<-"All Negative"
  174. data1["phen1"]<-"PD1"
  175. data1[!grepl("PD1+", data1$phen),"phen1"]<-NA
  176. data1["phen2"]<-"TIM3"
  177. data1[!grepl("TIM3+", data1$phen),"phen2"]<-NA
  178. data1["phen3"]<-"CTLA4"
  179. data1[!grepl("CTLA4+", data1$phen),"phen3"]<-NA
  180. data1["phen4"]<-"LAG3"
  181. data1[!grepl("LAG3+", data1$phen),"phen4"]<-NA
  182. data1<-data1 %>% arrange(desc(value))
  183. data2<-data1 %>% filter(!phen %in% c("All Negative","Other"))
  184. data1<-rbind(data2, data1 %>% filter(phen %in% c("All Negative","Other")) %>% arrange(desc(phen)))
  185. data_cd8<-data1 %>% filter(Population == "CD8")
  186. data_cd4<-data1 %>% filter(Population == "CD4")
  187. data_cd8$ymax<-cumsum(data_cd8$value)
  188. data_cd8$ymin<-c(0, head(data_cd8$ymax, n=-1))
  189. data_cd4$ymax<-cumsum(data_cd4$value)
  190. data_cd4$ymin<-c(0, head(data_cd4$ymax, n=-1))
  191. data1<-rbind(data_cd8, data_cd4)
  192. color<-c(c("CTLA4+ LAG3+ PD1+ TIM3+"="black","All Negative"="grey90","Other"="grey50", "PD1+"="#C07AFF", "CTLA4+"="#3EB3DE","TIM3+"="#5EF551","LAG3+"="#DEBB3E"),
  193. c("CTLA4+ PD1+"="#6666FF","PD1+ TIM3+"="#849CA8", "LAG3+ PD1+"="#C47F9F", "CTLA4+ TIM3+"="#4ED498", "CTLA4+ LAG3+"="#8EB78E", "LAG3+ TIM3+"="#9ED848"),
  194. c("CTLA4+ PD1+ TIM3+"="#B81515", "LAG3+ PD1+ TIM3+"="#0f5860"))
  195. basic.color<-color[c("PD1+","TIM3+","CTLA4+","LAG3+")]
  196. names(basic.color)<-c("PD1","TIM3","CTLA4","LAG3")
  197. # Make the plot
  198. g_coex<-ggplot(data1)+
  199. facet_grid(.~factor(Population, levels=c("CD8","CD4")))+
  200. geom_rect(aes(ymax=ymax, ymin=ymin, xmax=4.5, xmin=0), fill=color[data1$phen])+
  201. geom_rect(aes(ymax=ymax, ymin=ymin, xmax=5.4, xmin=5, fill=factor(phen1, levels=c("PD1","TIM3","CTLA4","LAG3"))))+
  202. geom_rect(aes(ymax=ymax, ymin=ymin, xmax=5.9, xmin=5.5, fill=factor(phen2, levels=c("PD1","TIM3","CTLA4","LAG3"))))+
  203. geom_rect(aes(ymax=ymax, ymin=ymin, xmax=6.4, xmin=6, fill=factor(phen3, levels=c("PD1","TIM3","CTLA4","LAG3"))))+
  204. geom_rect(aes(ymax=ymax, ymin=ymin, xmax=6.9, xmin=6.5, fill=factor(phen4, levels=c("PD1","TIM3","CTLA4","LAG3"))))+
  205. scale_fill_manual(values = basic.color, na.value="#FFFFFF00", drop=F, limits=c("PD1","TIM3","CTLA4","LAG3"), name="IC")+
  206. coord_polar(theta="y") + # Try to remove that to understand how the chart is built initially
  207. xlim(c(0, 8)) +# Try to remove that to see how to make a pie chart
  208. theme_classic()+
  209. theme(strip.background = element_blank(),
  210. strip.text = element_text(size=12, face="bold"),
  211. axis.line = element_blank(),
  212. axis.ticks = element_blank(),
  213. # plot.margin = margin(-200,0,0,0),
  214. axis.text = element_blank())
  215. nodes<-gs_get_pop_paths(gs)
  216. nodes_parent<-nodes[!grepl("CTLA4|LAG3|PD1|TIM3|root$", nodes)]
  217. nodes_cd4<-nodes[grepl("CTLA4$|LAG3$|PD1$|TIM3$", nodes) & grepl("/_CD4/",nodes)]
  218. nodes_cd8<-nodes[grepl("CTLA4$|LAG3$|PD1$|TIM3$", nodes) & grepl("/_CD8/",nodes)]
  219. g1<-ggcyto_arrange(autoplot(gs[[id]], nodes_parent), nrow=2)
  220. g2<-ggcyto_arrange(autoplot(gs[[id]], nodes_cd8), nrow=1)
  221. g3<-ggcyto_arrange(autoplot(gs[[id]], nodes_cd4), nrow=1)
  222. g_dots<-gridExtra::gtable_rbind(g1,g2,g3)
  223. g_all<-ggpubr::ggarrange(g_dots, g_coex, ncol=1, heights=c(0.75,0.25))
  224. ggsave(paste0(route,id,".IC.png"), g_all, width = 10, height = 14)
  225. ggsave(paste0(db_path, "Informes/",id,".IC.png"), g_all, width = 10, height = 14)
  226. }
  227. print("Informes finalizados!")
  228. }
  229. })
  230. observeEvent(input$popexport,{
  231. if (input$phenotype == "Pop"){
  232. route<-cito_dir
  233. ws<-open_flowjo_xml(paste0(route,"Populations.wsp"))
  234. gs<-flowjo_to_gatingset(ws, name="All Samples")
  235. sampleNames(gs)<-sapply(sampleNames(gs), function(x) strsplit(x, "Pop ")[[1]][2]) %>%
  236. gsub("[[:space:]][0-9]*.fcs_.[0-9]*","", . , perl = T)
  237. nodes<-sapply(strsplit(gs_get_pop_paths(gs), "/"), tail, 1)
  238. nodes<-nodes[grepl("_",nodes)]
  239. pop<-gs_pop_get_stats(gs, nodes=nodes,type="percent") %>% as.data.frame %>% mutate(percent=percent*100)
  240. pop[,"pop"]<-gsub("_","",pop$pop)
  241. pop$pop<-gsub(" ","_",pop$pop)
  242. pop$pop<-gsub("+","pos",pop$pop, fixed=T)
  243. pop$pop<-gsub("-","neg",pop$pop, fixed=T)
  244. pop<-rename(pop, "samples"="sample")
  245. pop$percent<-round(pop$percent, digits=2)
  246. pop_sp<-pop %>% spread(pop, percent)
  247. pop_sql<-sqlFetch(dta, "POPULATIONS") %>% slice(0)
  248. pop_sp<-pop_sp %>% merge(pop_sql, all=T) %>% select(colnames(pop_sql))
  249. vartypes<-rep("Number", pop_sp %>% select(-samples) %>% colnames %>% length)
  250. names(vartypes)<-pop_sp %>% select(-samples) %>% colnames
  251. sqlSave(dta, pop_sp, tablename="POPULATIONS", append = T, varTypes = vartypes, rownames = F)
  252. print("Tabla POPULATIONS sincronizada.")
  253. }
  254. if (input$phenotype == "Panel1"){
  255. route<-cito_dir
  256. ws<-open_flowjo_xml(paste0(route,"Panel1.wsp"))
  257. gs<-flowjo_to_gatingset(ws, name="All Samples")
  258. sampleNames(gs)<-sapply(sampleNames(gs), function(x) strsplit(x, "Panel1 ")[[1]][2]) %>%
  259. gsub("[[:space:]][0-9]*.fcs_.[0-9]*","", . , perl = T)
  260. gs<-gs[sampleNames(gs)[!grepl("Iso|ISO|iso",sampleNames(gs))]]
  261. bool.comb<-apply(
  262. expand.grid(c("","!"), c("","!"), c("","!"), c("","!")),
  263. 1,
  264. function(x) paste0(x[1],"CTLA4 & ",x[2],"LAG3 & ",x[3],"PD1 & ",x[4], "TIM3")
  265. )
  266. bool.name<-apply(
  267. expand.grid(c("+","-"), c("+","-"), c("+","-"), c("+","-")),
  268. 1,
  269. function(x) paste0("CTLA4",x[1]," LAG3",x[2]," PD1",x[3]," TIM3",x[4])
  270. )
  271. print("Booleanos CD8")
  272. for (i in 1:length(bool.comb)){
  273. call<-substitute(booleanFilter(v), list(v=as.symbol(bool.comb[i])))
  274. boolgate<-eval(call)
  275. gs_pop_add(gs, boolgate, parent="_CD8", name = bool.name[i])
  276. }
  277. print("Booleanos CD4")
  278. for (i in 1:length(bool.comb)){
  279. call<-substitute(booleanFilter(v), list(v=as.symbol(bool.comb[i])))
  280. boolgate<-eval(call)
  281. gs_pop_add(gs, boolgate, parent="_CD4", name = bool.name[i])
  282. }
  283. recompute(gs)
  284. names<-sampleNames(gs) # %>% gsub("ab|Ab|AB|iso|Iso|ISO| ","",.) %>% unique()
  285. nodes<-gs_get_pop_paths(gs)
  286. # nodes<-gsub("â\u0081»", "-", nodes)
  287. # nodes<-gsub("â\u0081º", "+", nodes)
  288. nodes<-nodes[grepl("CTLA4", nodes)]
  289. nodes<-nodes[!grepl("_CD4$|_CD8$|CTLA4$|TIM3$|PD1$|LAG3$", nodes)]
  290. pop<-gs_pop_get_stats(gs, nodes=nodes,type="percent") %>% as.data.frame %>% mutate(percent=percent*100)
  291. pop$percent<-round(pop$percent, digits=2)
  292. # pop$pop<-gsub("â\u0081»", "n", pop$pop)
  293. # pop$pop<-gsub("â\u0081º", "p", pop$pop)
  294. pop$pop<-gsub("-", "n", pop$pop, fixed=T)
  295. pop$pop<-gsub("+", "p", pop$pop, fixed=T)
  296. pop$pop<-gsub(" ", "_", pop$pop)
  297. ## Esto si no hay isotipo
  298. pop_sp<-pop
  299. pop_sp["Population"]<-str_extract(pop_sp$pop, "/_CD[4,8]{1}/") %>% gsub("/|_","",.)
  300. pop_sp$pop<-sapply(strsplit(pop_sp$pop, "/"), tail, 1)
  301. pop_sp<-pop_sp %>% spread(pop, percent)
  302. ## Esto si hay Isotipo
  303. # pop["Type"]<-"ab"
  304. # pop[grepl("iso|ISO|Iso",pop$sample),"Type"]<-"iso"
  305. # pop$sample<-gsub("iso|ISO|Iso|ab|AB|Ab| ","",pop$sample)
  306. # pop_sp<-pop %>% spread(Type, percent)
  307. # pop_sp["Net"]<-pop_sp$ab
  308. # pop_sp[!grepl("CTLA4n_LAG3n_PD1n_TIGITn_TIM3n",pop_sp$pop),"Net"]<-pop_sp[!grepl("CTLA4n_LAG3n_PD1n_TIGITn_TIM3n",pop_sp$pop),"ab"]-pop_sp[!grepl("CTLA4n_LAG3n_PD1n_TIGITn_TIM3n",pop_sp$pop),"iso"]
  309. # pop_sp$Net[pop_sp$Net < 0]<-0
  310. # pop_sp["Population"]<-str_extract(pop_sp$pop, "/CD[4,8]{1}/") %>% gsub("/","",.)
  311. # pop_sp$pop<-sapply(strsplit(pop_sp$pop, "/"), tail, 1)
  312. #
  313. # pop_sp<-pop_sp %>% select(-ab,-iso) %>% spread(pop,Net)
  314. # pop_sp$CTLA4n_LAG3n_PD1n_TIGITn_TIM3n<- pop_sp %>% select(-CTLA4n_LAG3n_PD1n_TIGITn_TIM3n) %>% group_by(sample,Population) %>%
  315. # gather(pop, value, -sample,-Population) %>% summarise(n=100-sum(value)) %>% pull(n)
  316. # if (input$dbtype == "OV"){
  317. # pop_sp <- rename(pop_sp, "samples"="sample")
  318. # }
  319. # if (input$dbtype %in% c("UM", "CC")){
  320. # pop_sp <- rename(pop_sp, "CODIGO"="sample")
  321. #
  322. pop_sql<-read.xlsx(paste0(db_path,"Panel1.xlsx"), sheet = "IC")
  323. pop_sp<-pop_sp %>% merge(pop_sql %>% slice(0), all=T) %>% select(colnames(pop_sql))
  324. ic_sp<-rbind(pop_sql, pop_sp)
  325. nodes<-sapply(strsplit(gs_get_pop_paths(gs), "/"), tail, 1)
  326. nodes<-gs_get_pop_paths(gs)[grepl("_",nodes)]
  327. pop<-gs_pop_get_stats(gs, nodes=nodes,type="percent") %>% as.data.frame %>% mutate(percent=percent*100)
  328. pop<-pop %>% mutate(Population=str_extract(pop, "/_CD[4,8]{1}/"),
  329. Population=case_when(is.na(Population)~"",
  330. Population == "/_CD4/"~"_CD4",
  331. Population == "/_CD8/"~"_CD8",
  332. TRUE~Population),
  333. pop=gsub("_","",pop),
  334. pop=paste0(pop,Population)) %>% select(-Population)
  335. pop$pop<-sapply(strsplit(pop$pop, "/"), tail, 1)
  336. pop$pop<-gsub(" ","_",pop$pop)
  337. # pop$pop<-gsub("+","pos",pop$pop, fixed=T)
  338. # pop$pop<-gsub("-","neg",pop$pop, fixed=T)
  339. # pop<-rename(pop, "samples"="sample")
  340. pop$percent<-round(pop$percent, digits=2)
  341. pop_sp<-pop %>% spread(pop, percent)
  342. pop_sql<-read.xlsx(paste0(db_path,"Panel1.xlsx"), sheet = "POPULATIONS")
  343. pop_sp<-pop_sp %>% merge(pop_sql %>% slice(0), all=T) %>% select(colnames(pop_sql))
  344. pop_sp<-rbind(pop_sql,pop_sp)
  345. write.xlsx(list("IC"=ic_sp, "POPULATIONS"=pop_sp), paste0(db_path, "Panel1.xlsx"))
  346. print("Tabla Panel1 sincronizada.")
  347. }
  348. })
  349. }
  350. # Run the application
  351. shinyApp(ui = ui, server = server)