library(ggplot2)
library(lubridate)
##
## Adjuntando el paquete: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(dplyr)
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(forecast)
## Warning: package 'forecast' was built under R version 4.4.3
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(tidytext)
## Warning: package 'tidytext' was built under R version 4.4.3
library(corrplot)
## Warning: package 'corrplot' was built under R version 4.4.3
## corrplot 0.95 loaded
library(textdata)
## Warning: package 'textdata' was built under R version 4.4.3
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ purrr 1.0.4 ✔ tibble 3.2.1
## ✔ readr 2.1.5 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(wordcloud)
## Warning: package 'wordcloud' was built under R version 4.4.3
## Cargando paquete requerido: RColorBrewer
library(readxl)
## Warning: package 'readxl' was built under R version 4.4.3
library(stopwords)
## Warning: package 'stopwords' was built under R version 4.4.3
library(RColorBrewer)
library(stringr)
Cargar datos
PS <- read_excel("C:\\Users\\Luis Mendoza\\Downloads\\Encuesta Perfil y SatisfacciĂ³n 2024.xlsx")
## Warning: Expecting numeric in T1172 / R1172C20: got 'Cada semana por trabajo'
colnames(PS)
## [1] "Mes" "Procedencia PaĂs"
## [3] "Procedencia Ciudad" "Edad"
## [5] "Motivo Visita" "Motivo Visita Otro"
## [7] "Noches Hospedadas" "Hospedaje en Hotel"
## [9] "Hospedaje en Extra Hotelero" "Hospedaje en Casa Familia/amigos"
## [11] "No se hospedĂ³" "Hospedaje en Otro"
## [13] "Transporte AviĂ³n" "Transporte AutobĂºs"
## [15] "Transporte AutomĂ³vil" "Transporte Motocicleta"
## [17] "Transporte Otro" "Gasto total en pesos"
## [19] "Primera Visita" "Visitas Previas"
## [21] "EV Experiencia" "EV Sostenibilidad"
## [23] "Experiencia" "Si Recomienda"
## [25] "No Recomienda" "Comentario RecomendaciĂ³n"
I. Transporte mĂ¡s utilizado
PS_transporte <- PS %>%
pivot_longer(cols = starts_with("Transporte "),
names_to = "TipoTransporte",
values_to = "valor") %>%
filter(!is.na(valor) & valor > 0)
transporte_total <- PS_transporte %>%
group_by(TipoTransporte) %>%
summarise(Cantidad = n()) %>%
mutate(TipoTransporte = str_remove(TipoTransporte, "Transporte "))
ggplot(transporte_total, aes(x = reorder(TipoTransporte, -Cantidad), y = Cantidad)) +
geom_col(fill = "steelblue") +
labs(title = "Transporte mĂ¡s utilizado (Todos los visitantes)", x = "Transporte", y = "Cantidad de personas") +
theme_minimal()

PS_extranjeros <- PS %>% filter(`Procedencia PaĂs` != "MĂ©xico" & !is.na(`Procedencia PaĂs`))
transporte_extranjeros <- PS_extranjeros %>%
pivot_longer(cols = starts_with("Transporte "),
names_to = "TipoTransporte",
values_to = "valor") %>%
filter(!is.na(valor) & valor > 0) %>%
group_by(TipoTransporte) %>%
summarise(Cantidad = n()) %>%
mutate(TipoTransporte = str_remove(TipoTransporte, "Transporte "))
ggplot(transporte_extranjeros, aes(x = reorder(TipoTransporte, -Cantidad), y = Cantidad)) +
geom_col(fill = "darkorange") +
labs(title = "Transporte mĂ¡s utilizado (Visitantes extranjeros)", x = "Transporte", y = "Cantidad de personas") +
theme_minimal()

II. AnĂ¡lisis Exploratorio de Datos
# 1. Definir nombres de meses vĂ¡lidos
meses_es <- c("enero", "febrero", "marzo", "abril", "mayo", "junio",
"julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre")
# 2. Normalizar y convertir "Mes" a fecha
PS$Mes <- tolower(trimws(PS$Mes)) # limpiar texto
PS$MesNum <- match(PS$Mes, meses_es) # convertir a nĂºmero de mes
PS$MesFecha <- as.Date(ISOdate(2024, PS$MesNum, 1)) # crear fecha segura
# 3. Filtrar registros vĂ¡lidos
PS <- PS[!is.na(PS$MesFecha), ]
# 4. Clasificar visitantes como "Normal" u "Outlier" segĂºn gasto
q3 <- quantile(PS$`Gasto total en pesos`, 0.75, na.rm = TRUE)
iqr <- IQR(PS$`Gasto total en pesos`, na.rm = TRUE)
limite_superior <- q3 + 1.5 * iqr
PS <- PS %>%
mutate(GrupoGasto = ifelse(`Gasto total en pesos` > limite_superior, "Outlier", "Normal"))
# 5. Calcular gasto total mensual por grupo
gasto_mensual_segmentado <- PS %>%
group_by(MesFecha, GrupoGasto) %>%
summarise(GastoTotal = sum(`Gasto total en pesos`, na.rm = TRUE)) %>%
ungroup()
## `summarise()` has grouped output by 'MesFecha'. You can override using the
## `.groups` argument.
# 6. Graficar tendencia mensual por grupo
ggplot(gasto_mensual_segmentado, aes(x = MesFecha, y = GastoTotal, color = GrupoGasto)) +
geom_line(size = 1.2) +
geom_smooth(se = FALSE, linetype = "dashed") +
scale_x_date(date_labels = "%b", date_breaks = "1 month") +
labs(title = "Tendencia mensual del gasto total por grupo de gasto",
x = "Mes", y = "Gasto total (MXN)", color = "Grupo de gasto") +
theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

# Agregar una etiqueta para distinguir grupos
PS <- PS %>%
mutate(
GrupoGasto = ifelse(`Gasto total en pesos` > limite_superior, "Outliers", "Normales")
)
# Eliminar valores NA en gasto
PS_filtrado <- PS %>% filter(!is.na(`Gasto total en pesos`))
# Boxplot comparativo
library(ggplot2)
ggplot(PS_filtrado, aes(x = GrupoGasto, y = `Gasto total en pesos`, fill = GrupoGasto)) +
geom_boxplot(outlier.color = "red", outlier.shape = 16) +
scale_y_continuous(labels = scales::comma) +
labs(
title = "ComparaciĂ³n del Gasto Total",
x = "Grupo",
y = "Gasto total en pesos (MXN)"
) +
theme_minimal()

# ------------------------
# Definir nombres legibles para las variables
# ------------------------
nombre_vars <- c("Gasto", "Edad", "Noches", "Visitas")
# ------------------------
# 1. Matriz general
# ------------------------
vars_cor <- PS[, c("Gasto total en pesos", "Edad", "Noches Hospedadas", "Visitas Previas")] %>%
mutate_all(as.numeric)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Edad = .Primitive("as.double")(Edad)`.
## Caused by warning:
## ! NAs introducidos por coerciĂ³n
colnames(vars_cor) <- nombre_vars
cor_mat <- cor(vars_cor, use = "complete.obs")
corrplot(cor_mat,
method = "circle",
type = "lower",
tl.col = "black",
tl.cex = 1,
tl.srt = 45,
addCoef.col = "black",
number.cex = 0.7,
col = colorRampPalette(c("firebrick", "white", "steelblue"))(200),
title = "Matriz de CorrelaciĂ³n General",
mar = c(0, 0, 2, 0))

# ------------------------
# 2. Matriz para visitantes normales
# ------------------------
normales <- PS %>%
filter(GrupoGasto == "Normales") %>%
select(`Gasto total en pesos`, Edad, `Noches Hospedadas`, `Visitas Previas`) %>%
mutate_all(as.numeric)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Edad = .Primitive("as.double")(Edad)`.
## Caused by warning:
## ! NAs introducidos por coerciĂ³n
colnames(normales) <- nombre_vars
cor_normales <- cor(normales, use = "complete.obs")
corrplot(cor_normales,
method = "circle",
type = "lower",
tl.col = "black",
tl.cex = 1,
tl.srt = 45,
addCoef.col = "black",
number.cex = 0.7,
col = colorRampPalette(c("firebrick", "white", "steelblue"))(200),
title = "CorrelaciĂ³n - Visitantes Normales",
mar = c(0, 0, 2, 0))

# ------------------------
# 3. Matriz para outliers
# ------------------------
outliers <- PS %>%
filter(GrupoGasto == "Outliers") %>%
select(`Gasto total en pesos`, Edad, `Noches Hospedadas`, `Visitas Previas`) %>%
mutate_all(as.numeric)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Edad = .Primitive("as.double")(Edad)`.
## Caused by warning:
## ! NAs introducidos por coerciĂ³n
colnames(outliers) <- nombre_vars
cor_outliers <- cor(outliers, use = "complete.obs")
corrplot(cor_outliers,
method = "circle",
type = "lower",
tl.col = "black",
tl.cex = 1,
tl.srt = 45,
addCoef.col = "black",
number.cex = 0.7,
col = colorRampPalette(c("firebrick", "white", "steelblue"))(200),
title = "CorrelaciĂ³n - Outliers",
mar = c(0, 0, 2, 0))

V. AnĂ¡lisis de Sentimiento
comentarios <- PS %>%
filter(!is.na(`Comentario RecomendaciĂ³n`)) %>%
tidytext::unnest_tokens(input = `Comentario RecomendaciĂ³n`, output = word) %>%
anti_join(get_stopwords(language = "es"))
## Joining with `by = join_by(word)`
lexico_es <- tibble(
word = c("bueno", "excelente", "agradable", "recomendado", "feliz", "malo", "terrible", "sucio", "caro", "pésimo"),
sentiment = c("positivo", "positivo", "positivo", "positivo", "positivo", "negativo", "negativo", "negativo", "negativo", "negativo")
)
sentimientos <- comentarios %>%
inner_join(lexico_es, by = "word") %>%
count(sentiment, sort = TRUE)
ggplot(sentimientos, aes(x = reorder(sentiment, n), y = n, fill = sentiment)) +
geom_col() +
coord_flip() +
labs(title = "AnĂ¡lisis de sentimiento de los comentarios (Español)", x = "Sentimiento", y = "Frecuencia") +
theme_minimal()

VI. Nube de Palabras por RecomendaciĂ³n
# -----------------------------
# Limpieza y procesamiento - SĂ Recomienda
# -----------------------------
recomienda <- PS %>%
filter(!is.na(`Si Recomienda`)) %>%
select(`Si Recomienda`) %>%
rename(texto = `Si Recomienda`) %>%
unnest_tokens(word, texto) %>%
filter(!word %in% stopwords::stopwords("es"),
!str_detect(word, "\\d"), # elimina nĂºmeros
str_length(word) > 2, # evita palabras cortas irrelevantes
!is.na(word)) %>%
mutate(word = case_when( # corregir sinĂ³nimos o variantes
word == "banio" ~ "baño",
word == "limpia" ~ "limpio",
word == "recomiendo" ~ "recomendar",
TRUE ~ word
)) %>%
count(word, sort = TRUE)
# -----------------------------
# Limpieza y procesamiento - No Recomienda
# -----------------------------
no_recomienda <- PS %>%
filter(!is.na(`No Recomienda`)) %>%
select(`No Recomienda`) %>%
rename(texto = `No Recomienda`) %>%
unnest_tokens(word, texto) %>%
filter(!word %in% stopwords::stopwords("es"),
!str_detect(word, "\\d"),
str_length(word) > 2,
!is.na(word)) %>%
mutate(word = case_when(
word == "banio" ~ "baño",
word == "sucio" ~ "suciedad",
TRUE ~ word
)) %>%
count(word, sort = TRUE)
# -----------------------------
# Graficar nubes de palabras mejoradas
# -----------------------------
par(mfrow = c(1, 2)) # panel de 2 grĂ¡ficos
# Wordcloud para SĂ Recomienda
if (nrow(recomienda) > 0) {
wordcloud(words = recomienda$word,
freq = recomienda$n,
scale = c(4, 0.8),
max.words = 100,
colors = brewer.pal(8, "Dark2"),
random.order = FALSE)
title("Lugares que SĂ Recomiendan")
} else {
plot.new(); title("Sin datos: SĂ Recomienda")
}
# Wordcloud para No Recomienda
if (nrow(no_recomienda) > 0) {
wordcloud(words = no_recomienda$word,
freq = no_recomienda$n,
scale = c(4, 0.8),
max.words = 100,
colors = brewer.pal(8, "Reds"),
random.order = FALSE)
title("Lugares que No Recomiendan")
} else {
plot.new(); title("Sin datos: No Recomienda")
}

par(mfrow = c(1, 1)) # restaurar panel normal
LS0tDQp0aXRsZTogIkFuw6FsaXNpcyBkZSBUdXJpc21vIGVuIGxhIFpvbmEgTWV0cm9wb2xpdGFuYSBkZSBNb250ZXJyZXkiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgdGhlbWU6IGNlcnVsZWFuDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KYGBge3IgbGlicmVyaWFzfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShmb3JlY2FzdCkNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoc3RvcHdvcmRzKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KHN0cmluZ3IpDQoNCmBgYA0KDQojIyBDYXJnYXIgZGF0b3MNCg0KYGBge3IgY2FyZ2FyLWRhdG9zfQ0KUFMgPC0gcmVhZF9leGNlbCgiQzpcXFVzZXJzXFxMdWlzIE1lbmRvemFcXERvd25sb2Fkc1xcRW5jdWVzdGEgUGVyZmlsIHkgU2F0aXNmYWNjacOzbiAyMDI0Lnhsc3giKQ0KY29sbmFtZXMoUFMpDQpgYGANCg0KIyMgSS4gVHJhbnNwb3J0ZSBtw6FzIHV0aWxpemFkbw0KDQpgYGB7ciB0cmFuc3BvcnRlfQ0KUFNfdHJhbnNwb3J0ZSA8LSBQUyAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgiVHJhbnNwb3J0ZSAiKSwgDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJUaXBvVHJhbnNwb3J0ZSIsIA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbG9yIikgJT4lDQogIGZpbHRlcighaXMubmEodmFsb3IpICYgdmFsb3IgPiAwKQ0KDQp0cmFuc3BvcnRlX3RvdGFsIDwtIFBTX3RyYW5zcG9ydGUgJT4lDQogIGdyb3VwX2J5KFRpcG9UcmFuc3BvcnRlKSAlPiUNCiAgc3VtbWFyaXNlKENhbnRpZGFkID0gbigpKSAlPiUNCiAgbXV0YXRlKFRpcG9UcmFuc3BvcnRlID0gc3RyX3JlbW92ZShUaXBvVHJhbnNwb3J0ZSwgIlRyYW5zcG9ydGUgIikpDQoNCmdncGxvdCh0cmFuc3BvcnRlX3RvdGFsLCBhZXMoeCA9IHJlb3JkZXIoVGlwb1RyYW5zcG9ydGUsIC1DYW50aWRhZCksIHkgPSBDYW50aWRhZCkpICsNCiAgZ2VvbV9jb2woZmlsbCA9ICJzdGVlbGJsdWUiKSArDQogIGxhYnModGl0bGUgPSAiVHJhbnNwb3J0ZSBtw6FzIHV0aWxpemFkbyAoVG9kb3MgbG9zIHZpc2l0YW50ZXMpIiwgeCA9ICJUcmFuc3BvcnRlIiwgeSA9ICJDYW50aWRhZCBkZSBwZXJzb25hcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNClBTX2V4dHJhbmplcm9zIDwtIFBTICU+JSBmaWx0ZXIoYFByb2NlZGVuY2lhIFBhw61zYCAhPSAiTcOpeGljbyIgJiAhaXMubmEoYFByb2NlZGVuY2lhIFBhw61zYCkpDQoNCnRyYW5zcG9ydGVfZXh0cmFuamVyb3MgPC0gUFNfZXh0cmFuamVyb3MgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoIlRyYW5zcG9ydGUgIiksIA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiVGlwb1RyYW5zcG9ydGUiLCANCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWxvciIpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHZhbG9yKSAmIHZhbG9yID4gMCkgJT4lDQogIGdyb3VwX2J5KFRpcG9UcmFuc3BvcnRlKSAlPiUNCiAgc3VtbWFyaXNlKENhbnRpZGFkID0gbigpKSAlPiUNCiAgbXV0YXRlKFRpcG9UcmFuc3BvcnRlID0gc3RyX3JlbW92ZShUaXBvVHJhbnNwb3J0ZSwgIlRyYW5zcG9ydGUgIikpDQoNCmdncGxvdCh0cmFuc3BvcnRlX2V4dHJhbmplcm9zLCBhZXMoeCA9IHJlb3JkZXIoVGlwb1RyYW5zcG9ydGUsIC1DYW50aWRhZCksIHkgPSBDYW50aWRhZCkpICsNCiAgZ2VvbV9jb2woZmlsbCA9ICJkYXJrb3JhbmdlIikgKw0KICBsYWJzKHRpdGxlID0gIlRyYW5zcG9ydGUgbcOhcyB1dGlsaXphZG8gKFZpc2l0YW50ZXMgZXh0cmFuamVyb3MpIiwgeCA9ICJUcmFuc3BvcnRlIiwgeSA9ICJDYW50aWRhZCBkZSBwZXJzb25hcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgSUkuIEFuw6FsaXNpcyBFeHBsb3JhdG9yaW8gZGUgRGF0b3MNCg0KYGBge3IgdGVuZGVuY2lhLWdhc3RvfQ0KIyAxLiBEZWZpbmlyIG5vbWJyZXMgZGUgbWVzZXMgdsOhbGlkb3MNCm1lc2VzX2VzIDwtIGMoImVuZXJvIiwgImZlYnJlcm8iLCAibWFyem8iLCAiYWJyaWwiLCAibWF5byIsICJqdW5pbyIsDQogICAgICAgICAgICAgICJqdWxpbyIsICJhZ29zdG8iLCAic2VwdGllbWJyZSIsICJvY3R1YnJlIiwgIm5vdmllbWJyZSIsICJkaWNpZW1icmUiKQ0KDQojIDIuIE5vcm1hbGl6YXIgeSBjb252ZXJ0aXIgIk1lcyIgYSBmZWNoYQ0KUFMkTWVzIDwtIHRvbG93ZXIodHJpbXdzKFBTJE1lcykpICAgICAgICAgICAgICAgICMgbGltcGlhciB0ZXh0bw0KUFMkTWVzTnVtIDwtIG1hdGNoKFBTJE1lcywgbWVzZXNfZXMpICAgICAgICAgICAgICMgY29udmVydGlyIGEgbsO6bWVybyBkZSBtZXMNClBTJE1lc0ZlY2hhIDwtIGFzLkRhdGUoSVNPZGF0ZSgyMDI0LCBQUyRNZXNOdW0sIDEpKSAgIyBjcmVhciBmZWNoYSBzZWd1cmENCg0KIyAzLiBGaWx0cmFyIHJlZ2lzdHJvcyB2w6FsaWRvcw0KUFMgPC0gUFNbIWlzLm5hKFBTJE1lc0ZlY2hhKSwgXQ0KDQojIDQuIENsYXNpZmljYXIgdmlzaXRhbnRlcyBjb21vICJOb3JtYWwiIHUgIk91dGxpZXIiIHNlZ8O6biBnYXN0bw0KcTMgPC0gcXVhbnRpbGUoUFMkYEdhc3RvIHRvdGFsIGVuIHBlc29zYCwgMC43NSwgbmEucm0gPSBUUlVFKQ0KaXFyIDwtIElRUihQUyRgR2FzdG8gdG90YWwgZW4gcGVzb3NgLCBuYS5ybSA9IFRSVUUpDQpsaW1pdGVfc3VwZXJpb3IgPC0gcTMgKyAxLjUgKiBpcXINCg0KUFMgPC0gUFMgJT4lDQogIG11dGF0ZShHcnVwb0dhc3RvID0gaWZlbHNlKGBHYXN0byB0b3RhbCBlbiBwZXNvc2AgPiBsaW1pdGVfc3VwZXJpb3IsICJPdXRsaWVyIiwgIk5vcm1hbCIpKQ0KDQojIDUuIENhbGN1bGFyIGdhc3RvIHRvdGFsIG1lbnN1YWwgcG9yIGdydXBvDQpnYXN0b19tZW5zdWFsX3NlZ21lbnRhZG8gPC0gUFMgJT4lDQogIGdyb3VwX2J5KE1lc0ZlY2hhLCBHcnVwb0dhc3RvKSAlPiUNCiAgc3VtbWFyaXNlKEdhc3RvVG90YWwgPSBzdW0oYEdhc3RvIHRvdGFsIGVuIHBlc29zYCwgbmEucm0gPSBUUlVFKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQojIDYuIEdyYWZpY2FyIHRlbmRlbmNpYSBtZW5zdWFsIHBvciBncnVwbw0KZ2dwbG90KGdhc3RvX21lbnN1YWxfc2VnbWVudGFkbywgYWVzKHggPSBNZXNGZWNoYSwgeSA9IEdhc3RvVG90YWwsIGNvbG9yID0gR3J1cG9HYXN0bykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJWIiLCBkYXRlX2JyZWFrcyA9ICIxIG1vbnRoIikgKw0KICBsYWJzKHRpdGxlID0gIlRlbmRlbmNpYSBtZW5zdWFsIGRlbCBnYXN0byB0b3RhbCBwb3IgZ3J1cG8gZGUgZ2FzdG8iLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJHYXN0byB0b3RhbCAoTVhOKSIsIGNvbG9yID0gIkdydXBvIGRlIGdhc3RvIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCmBgYHtyIGJveHBsb3QtZ2FzdG99DQoNCg0KIyBBZ3JlZ2FyIHVuYSBldGlxdWV0YSBwYXJhIGRpc3Rpbmd1aXIgZ3J1cG9zDQpQUyA8LSBQUyAlPiUNCiAgbXV0YXRlKA0KICAgIEdydXBvR2FzdG8gPSBpZmVsc2UoYEdhc3RvIHRvdGFsIGVuIHBlc29zYCA+IGxpbWl0ZV9zdXBlcmlvciwgIk91dGxpZXJzIiwgIk5vcm1hbGVzIikNCiAgKQ0KDQojIEVsaW1pbmFyIHZhbG9yZXMgTkEgZW4gZ2FzdG8NClBTX2ZpbHRyYWRvIDwtIFBTICU+JSBmaWx0ZXIoIWlzLm5hKGBHYXN0byB0b3RhbCBlbiBwZXNvc2ApKQ0KDQojIEJveHBsb3QgY29tcGFyYXRpdm8NCmxpYnJhcnkoZ2dwbG90MikNCmdncGxvdChQU19maWx0cmFkbywgYWVzKHggPSBHcnVwb0dhc3RvLCB5ID0gYEdhc3RvIHRvdGFsIGVuIHBlc29zYCwgZmlsbCA9IEdydXBvR2FzdG8pKSArDQogIGdlb21fYm94cGxvdChvdXRsaWVyLmNvbG9yID0gInJlZCIsIG91dGxpZXIuc2hhcGUgPSAxNikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZWwgR2FzdG8gVG90YWwiLA0KICAgIHggPSAiR3J1cG8iLA0KICAgIHkgPSAiR2FzdG8gdG90YWwgZW4gcGVzb3MgKE1YTikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCmBgYHtyIG1hdHJpei1jb3JyZWxhY2lvbn0NCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIERlZmluaXIgbm9tYnJlcyBsZWdpYmxlcyBwYXJhIGxhcyB2YXJpYWJsZXMNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpub21icmVfdmFycyA8LSBjKCJHYXN0byIsICJFZGFkIiwgIk5vY2hlcyIsICJWaXNpdGFzIikNCg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgMS4gTWF0cml6IGdlbmVyYWwNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQp2YXJzX2NvciA8LSBQU1ssIGMoIkdhc3RvIHRvdGFsIGVuIHBlc29zIiwgIkVkYWQiLCAiTm9jaGVzIEhvc3BlZGFkYXMiLCAiVmlzaXRhcyBQcmV2aWFzIildICU+JQ0KICBtdXRhdGVfYWxsKGFzLm51bWVyaWMpDQpjb2xuYW1lcyh2YXJzX2NvcikgPC0gbm9tYnJlX3ZhcnMNCmNvcl9tYXQgPC0gY29yKHZhcnNfY29yLCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KY29ycnBsb3QoY29yX21hdCwNCiAgICAgICAgIG1ldGhvZCA9ICJjaXJjbGUiLA0KICAgICAgICAgdHlwZSA9ICJsb3dlciIsDQogICAgICAgICB0bC5jb2wgPSAiYmxhY2siLA0KICAgICAgICAgdGwuY2V4ID0gMSwNCiAgICAgICAgIHRsLnNydCA9IDQ1LA0KICAgICAgICAgYWRkQ29lZi5jb2wgPSAiYmxhY2siLA0KICAgICAgICAgbnVtYmVyLmNleCA9IDAuNywNCiAgICAgICAgIGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUoYygiZmlyZWJyaWNrIiwgIndoaXRlIiwgInN0ZWVsYmx1ZSIpKSgyMDApLA0KICAgICAgICAgdGl0bGUgPSAiTWF0cml6IGRlIENvcnJlbGFjacOzbiBHZW5lcmFsIiwNCiAgICAgICAgIG1hciA9IGMoMCwgMCwgMiwgMCkpDQoNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIDIuIE1hdHJpeiBwYXJhIHZpc2l0YW50ZXMgbm9ybWFsZXMNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpub3JtYWxlcyA8LSBQUyAlPiUNCiAgZmlsdGVyKEdydXBvR2FzdG8gPT0gIk5vcm1hbGVzIikgJT4lDQogIHNlbGVjdChgR2FzdG8gdG90YWwgZW4gcGVzb3NgLCBFZGFkLCBgTm9jaGVzIEhvc3BlZGFkYXNgLCBgVmlzaXRhcyBQcmV2aWFzYCkgJT4lDQogIG11dGF0ZV9hbGwoYXMubnVtZXJpYykNCmNvbG5hbWVzKG5vcm1hbGVzKSA8LSBub21icmVfdmFycw0KY29yX25vcm1hbGVzIDwtIGNvcihub3JtYWxlcywgdXNlID0gImNvbXBsZXRlLm9icyIpDQoNCmNvcnJwbG90KGNvcl9ub3JtYWxlcywNCiAgICAgICAgIG1ldGhvZCA9ICJjaXJjbGUiLA0KICAgICAgICAgdHlwZSA9ICJsb3dlciIsDQogICAgICAgICB0bC5jb2wgPSAiYmxhY2siLA0KICAgICAgICAgdGwuY2V4ID0gMSwNCiAgICAgICAgIHRsLnNydCA9IDQ1LA0KICAgICAgICAgYWRkQ29lZi5jb2wgPSAiYmxhY2siLA0KICAgICAgICAgbnVtYmVyLmNleCA9IDAuNywNCiAgICAgICAgIGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUoYygiZmlyZWJyaWNrIiwgIndoaXRlIiwgInN0ZWVsYmx1ZSIpKSgyMDApLA0KICAgICAgICAgdGl0bGUgPSAiQ29ycmVsYWNpw7NuIC0gVmlzaXRhbnRlcyBOb3JtYWxlcyIsDQogICAgICAgICBtYXIgPSBjKDAsIDAsIDIsIDApKQ0KDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyAzLiBNYXRyaXogcGFyYSBvdXRsaWVycw0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm91dGxpZXJzIDwtIFBTICU+JQ0KICBmaWx0ZXIoR3J1cG9HYXN0byA9PSAiT3V0bGllcnMiKSAlPiUNCiAgc2VsZWN0KGBHYXN0byB0b3RhbCBlbiBwZXNvc2AsIEVkYWQsIGBOb2NoZXMgSG9zcGVkYWRhc2AsIGBWaXNpdGFzIFByZXZpYXNgKSAlPiUNCiAgbXV0YXRlX2FsbChhcy5udW1lcmljKQ0KY29sbmFtZXMob3V0bGllcnMpIDwtIG5vbWJyZV92YXJzDQpjb3Jfb3V0bGllcnMgPC0gY29yKG91dGxpZXJzLCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KY29ycnBsb3QoY29yX291dGxpZXJzLA0KICAgICAgICAgbWV0aG9kID0gImNpcmNsZSIsDQogICAgICAgICB0eXBlID0gImxvd2VyIiwNCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIsDQogICAgICAgICB0bC5jZXggPSAxLA0KICAgICAgICAgdGwuc3J0ID0gNDUsDQogICAgICAgICBhZGRDb2VmLmNvbCA9ICJibGFjayIsDQogICAgICAgICBudW1iZXIuY2V4ID0gMC43LA0KICAgICAgICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJmaXJlYnJpY2siLCAid2hpdGUiLCAic3RlZWxibHVlIikpKDIwMCksDQogICAgICAgICB0aXRsZSA9ICJDb3JyZWxhY2nDs24gLSBPdXRsaWVycyIsDQogICAgICAgICBtYXIgPSBjKDAsIDAsIDIsIDApKQ0KDQpgYGANCg0KDQoNCiMjIFYuIEFuw6FsaXNpcyBkZSBTZW50aW1pZW50bw0KDQpgYGB7ciBzZW50aW1pZW50b30NCmNvbWVudGFyaW9zIDwtIFBTICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGBDb21lbnRhcmlvIFJlY29tZW5kYWNpw7NuYCkpICU+JQ0KICB0aWR5dGV4dDo6dW5uZXN0X3Rva2VucyhpbnB1dCA9IGBDb21lbnRhcmlvIFJlY29tZW5kYWNpw7NuYCwgb3V0cHV0ID0gd29yZCkgJT4lDQogIGFudGlfam9pbihnZXRfc3RvcHdvcmRzKGxhbmd1YWdlID0gImVzIikpDQoNCmxleGljb19lcyA8LSB0aWJibGUoDQogIHdvcmQgPSBjKCJidWVubyIsICJleGNlbGVudGUiLCAiYWdyYWRhYmxlIiwgInJlY29tZW5kYWRvIiwgImZlbGl6IiwgIm1hbG8iLCAidGVycmlibGUiLCAic3VjaW8iLCAiY2FybyIsICJww6lzaW1vIiksDQogIHNlbnRpbWVudCA9IGMoInBvc2l0aXZvIiwgInBvc2l0aXZvIiwgInBvc2l0aXZvIiwgInBvc2l0aXZvIiwgInBvc2l0aXZvIiwgIm5lZ2F0aXZvIiwgIm5lZ2F0aXZvIiwgIm5lZ2F0aXZvIiwgIm5lZ2F0aXZvIiwgIm5lZ2F0aXZvIikNCikNCg0Kc2VudGltaWVudG9zIDwtIGNvbWVudGFyaW9zICU+JQ0KICBpbm5lcl9qb2luKGxleGljb19lcywgYnkgPSAid29yZCIpICU+JQ0KICBjb3VudChzZW50aW1lbnQsIHNvcnQgPSBUUlVFKQ0KDQpnZ3Bsb3Qoc2VudGltaWVudG9zLCBhZXMoeCA9IHJlb3JkZXIoc2VudGltZW50LCBuKSwgeSA9IG4sIGZpbGwgPSBzZW50aW1lbnQpKSArDQogIGdlb21fY29sKCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHRpdGxlID0gIkFuw6FsaXNpcyBkZSBzZW50aW1pZW50byBkZSBsb3MgY29tZW50YXJpb3MgKEVzcGHDsW9sKSIsIHggPSAiU2VudGltaWVudG8iLCB5ID0gIkZyZWN1ZW5jaWEiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMjIFZJLiBOdWJlIGRlIFBhbGFicmFzIHBvciBSZWNvbWVuZGFjacOzbg0KDQpgYGB7ciBudWJlcy1wYWxhYnJhcywgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9DQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIExpbXBpZXphIHkgcHJvY2VzYW1pZW50byAtIFPDrSBSZWNvbWllbmRhDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpyZWNvbWllbmRhIDwtIFBTICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGBTaSBSZWNvbWllbmRhYCkpICU+JQ0KICBzZWxlY3QoYFNpIFJlY29taWVuZGFgKSAlPiUNCiAgcmVuYW1lKHRleHRvID0gYFNpIFJlY29taWVuZGFgKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0bykgJT4lDQogIGZpbHRlcighd29yZCAlaW4lIHN0b3B3b3Jkczo6c3RvcHdvcmRzKCJlcyIpLA0KICAgICAgICAgIXN0cl9kZXRlY3Qod29yZCwgIlxcZCIpLCAgICAgICAgICMgZWxpbWluYSBuw7ptZXJvcw0KICAgICAgICAgc3RyX2xlbmd0aCh3b3JkKSA+IDIsICAgICAgICAgICAgICMgZXZpdGEgcGFsYWJyYXMgY29ydGFzIGlycmVsZXZhbnRlcw0KICAgICAgICAgIWlzLm5hKHdvcmQpKSAlPiUNCiAgbXV0YXRlKHdvcmQgPSBjYXNlX3doZW4oICAgICAgICAgICAgICAgICMgY29ycmVnaXIgc2luw7NuaW1vcyBvIHZhcmlhbnRlcw0KICAgIHdvcmQgPT0gImJhbmlvIiB+ICJiYcOxbyIsDQogICAgd29yZCA9PSAibGltcGlhIiB+ICJsaW1waW8iLA0KICAgIHdvcmQgPT0gInJlY29taWVuZG8iIH4gInJlY29tZW5kYXIiLA0KICAgIFRSVUUgfiB3b3JkDQogICkpICU+JQ0KICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBMaW1waWV6YSB5IHByb2Nlc2FtaWVudG8gLSBObyBSZWNvbWllbmRhDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpub19yZWNvbWllbmRhIDwtIFBTICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGBObyBSZWNvbWllbmRhYCkpICU+JQ0KICBzZWxlY3QoYE5vIFJlY29taWVuZGFgKSAlPiUNCiAgcmVuYW1lKHRleHRvID0gYE5vIFJlY29taWVuZGFgKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0bykgJT4lDQogIGZpbHRlcighd29yZCAlaW4lIHN0b3B3b3Jkczo6c3RvcHdvcmRzKCJlcyIpLA0KICAgICAgICAgIXN0cl9kZXRlY3Qod29yZCwgIlxcZCIpLA0KICAgICAgICAgc3RyX2xlbmd0aCh3b3JkKSA+IDIsDQogICAgICAgICAhaXMubmEod29yZCkpICU+JQ0KICBtdXRhdGUod29yZCA9IGNhc2Vfd2hlbigNCiAgICB3b3JkID09ICJiYW5pbyIgfiAiYmHDsW8iLA0KICAgIHdvcmQgPT0gInN1Y2lvIiB+ICJzdWNpZWRhZCIsDQogICAgVFJVRSB+IHdvcmQNCiAgKSkgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIEdyYWZpY2FyIG51YmVzIGRlIHBhbGFicmFzIG1lam9yYWRhcw0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KcGFyKG1mcm93ID0gYygxLCAyKSkgICMgcGFuZWwgZGUgMiBncsOhZmljb3MNCg0KIyBXb3JkY2xvdWQgcGFyYSBTw60gUmVjb21pZW5kYQ0KaWYgKG5yb3cocmVjb21pZW5kYSkgPiAwKSB7DQogIHdvcmRjbG91ZCh3b3JkcyA9IHJlY29taWVuZGEkd29yZCwNCiAgICAgICAgICAgIGZyZXEgPSByZWNvbWllbmRhJG4sDQogICAgICAgICAgICBzY2FsZSA9IGMoNCwgMC44KSwNCiAgICAgICAgICAgIG1heC53b3JkcyA9IDEwMCwNCiAgICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIiksDQogICAgICAgICAgICByYW5kb20ub3JkZXIgPSBGQUxTRSkNCiAgdGl0bGUoIkx1Z2FyZXMgcXVlIFPDrSBSZWNvbWllbmRhbiIpDQp9IGVsc2Ugew0KICBwbG90Lm5ldygpOyB0aXRsZSgiU2luIGRhdG9zOiBTw60gUmVjb21pZW5kYSIpDQp9DQoNCiMgV29yZGNsb3VkIHBhcmEgTm8gUmVjb21pZW5kYQ0KaWYgKG5yb3cobm9fcmVjb21pZW5kYSkgPiAwKSB7DQogIHdvcmRjbG91ZCh3b3JkcyA9IG5vX3JlY29taWVuZGEkd29yZCwNCiAgICAgICAgICAgIGZyZXEgPSBub19yZWNvbWllbmRhJG4sDQogICAgICAgICAgICBzY2FsZSA9IGMoNCwgMC44KSwNCiAgICAgICAgICAgIG1heC53b3JkcyA9IDEwMCwNCiAgICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIlJlZHMiKSwNCiAgICAgICAgICAgIHJhbmRvbS5vcmRlciA9IEZBTFNFKQ0KICB0aXRsZSgiTHVnYXJlcyBxdWUgTm8gUmVjb21pZW5kYW4iKQ0KfSBlbHNlIHsNCiAgcGxvdC5uZXcoKTsgdGl0bGUoIlNpbiBkYXRvczogTm8gUmVjb21pZW5kYSIpDQp9DQoNCnBhcihtZnJvdyA9IGMoMSwgMSkpICAjIHJlc3RhdXJhciBwYW5lbCBub3JtYWwNCg0KYGBgDQoNCg==