encuestas = FileAttachment("datos/encuestas.csv").csv()
// Normalizar vacíos
esVacio = (v) => {
if (v === null || v === undefined) return true
const t = String(v).trim().toLowerCase()
return t === "" || t === "nan" || t === "null" || t === "none" || t === "s/n"
}
// Campos evaluados para completitud de encuestas
campos = [
"cobertura",
"autor",
"fecha_fin",
"fecha_inicio",
"margen",
"muestra",
"resultado",
"publicacion",
"nivel_confianza",
"archivo",
"casa_registrada",
"ciudadanos_registrados",
"censo_personas16",
"item",
"label",
"coberturaLabel",
"autorLabel"
]
totalCampos = campos.length
// Cálculo por encuesta
encuestasCompletitud = encuestas.map((e) => {
const llenos = campos.filter((k) => !esVacio(e[k])).length
const faltantes = totalCampos - llenos
const completitud = llenos / totalCampos
const camposFaltantes = campos.filter((k) => esVacio(e[k]))
const coberturaNombre = e.coberturaLabel || e.cobertura || "(Sin dato)"
const autorNombre = e.autorLabel || e.autor || "(Sin dato)"
const puntajeImportancia = faltantes
return {
...e,
llenos,
faltantes,
camposFaltantes,
coberturaNombre,
autorNombre,
puntajeImportancia,
completitud,
completitudPct: Math.round(completitud * 100)
}
})
totalEncuestas = encuestasCompletitud.length
promedioCompletitud = totalEncuestas
? encuestasCompletitud.reduce((acc, d) => acc + d.completitud, 0) / totalEncuestas
: 0
registrosCompletos = encuestasCompletitud.filter((d) => d.completitud === 1).length
sinArchivo = encuestasCompletitud.filter((d) => esVacio(d.archivo)).length
// Completitud por campo
completitudCampos = campos.map((campo) => {
const llenos = encuestasCompletitud.filter((d) => !esVacio(d[campo])).length
return {
campo,
llenos,
total: totalEncuestas,
pct: totalEncuestas ? (llenos / totalEncuestas) * 100 : 0
}
})
// Promedio por autor
completitudAutor = Array.from(
encuestasCompletitud.reduce((m, d) => {
const k = d.autorNombre
if (!m.has(k)) m.set(k, [])
m.get(k).push(d.completitud)
return m
}, new Map()),
([autor, valores]) => ({
autor,
pct: (valores.reduce((a, b) => a + b, 0) / valores.length) * 100
})
).sort((a, b) => b.pct - a.pct)
// Ranking con menor completitud
peorCompletitud = [...encuestasCompletitud]
.sort((a, b) => b.puntajeImportancia - a.puntajeImportancia)
.slice(0, 12)
coberturasDisponibles = ["(Todos)", ...new Set(encuestasCompletitud.map((d) => d.coberturaNombre))]
autoresDisponibles = ["(Todos)", ...new Set(encuestasCompletitud.map((d) => d.autorNombre))]Plot.plot({
height: 450,
marginLeft: 120,
x: {label: "Completitud (%)", domain: [0, 100]},
y: {label: null},
color: {legend: false},
marks: [
Plot.barX(completitudCampos, {
x: "pct",
y: "campo",
sort: {y: "x", reverse: true},
fill: (d) => (d.pct >= 80 ? "#198754" : d.pct >= 50 ? "#fd7e14" : "#dc3545"),
tip: true
}),
Plot.ruleX([0, 100])
]
})Plot.plot({
height: 400,
marginLeft: 140,
x: {label: "Completitud promedio (%)", domain: [0, 100]},
y: {label: null},
marks: [
Plot.barX(completitudAutor, {
x: "pct",
y: "autor",
fill: "#0d6efd",
sort: {y: "x", reverse: true},
tip: true
}),
Plot.text(completitudAutor, {
x: "pct",
y: "autor",
text: (d) => `${d.pct.toFixed(1)}%`,
dx: 8,
textAnchor: "start",
fill: "#212529"
})
]
})textoFiltro = (filtrosTabla.nombre || "").trim().toLowerCase()
encuestasFiltradas = encuestasCompletitud.filter((d) => {
const matchCobertura = filtrosTabla.cobertura === "(Todos)" || d.coberturaNombre === filtrosTabla.cobertura
const matchAutor = filtrosTabla.autor === "(Todos)" || d.autorNombre === filtrosTabla.autor
const matchNombre = !textoFiltro || (d.label || "").toLowerCase().includes(textoFiltro)
return matchCobertura && matchAutor && matchNombre
})Inputs.table(
encuestasFiltradas.map((d) => ({
label: d.label,
cobertura: d.coberturaNombre,
autor: d.autorNombre,
fechaInicio: d.fecha_inicio,
fechaFin: d.fecha_fin,
muestra: d.muestra,
margen: d.margen,
nivelConfianza: d.nivel_confianza,
archivo: d.archivo,
item: d.item,
puntosFaltantes: d.faltantes,
puntajeImportancia: d.puntajeImportancia,
completitud: `${d.completitudPct}%`,
faltantes: d.camposFaltantes.join(", ")
})),
{
columns: ["label", "cobertura", "autor", "fechaInicio", "fechaFin", "muestra", "margen", "nivelConfianza", "archivo", "item", "puntosFaltantes", "puntajeImportancia", "completitud", "faltantes"],
sort: "puntajeImportancia",
reverse: true,
format: {
archivo: (archivo) => archivo ? html`<a href="${archivo}" target="_blank">Ver archivo</a>` : "",
item: (item) => item ? html`<a href="https://graph.sociest.org/entity/${item}" target="_blank">Ver ficha</a>` : ""
}
}
)