El script 01_descarga_datos.R tiene como finalidad
realizar la adquisición masiva, controlada y reproducible de los
registros históricos de Saber 11 disponibles a través de la API de datos
abiertos de Socrata, almacenarlos progresivamente en formato Parquet y
verificar su integridad antes de consolidarlos en un único archivo de
trabajo.
A diferencia de una descarga simple, esta etapa incorpora controles explícitos sobre la continuidad de las páginas, la presencia y unicidad de los identificadores técnicos, los posibles solapamientos entre bloques, la correspondencia entre el número de registros almacenados y el número reportado por la fuente, y la estabilidad de los extremos de la extracción.
El principio metodológico central es:
La capa de adquisición debe conservar fielmente la información entregada por la fuente y demostrar, mediante controles reproducibles, que la extracción local es completa e íntegra antes de iniciar cualquier auditoría, limpieza o transformación.
Por esta razón, el script no modifica variables sustantivas de Saber
11. Su responsabilidad es construir una capa raw
verificable que pueda utilizarse posteriormente por
02_auditoria_datos.R.
El proceso utiliza principalmente los paquetes:
jsonlite, para consultar y procesar respuestas
JSON;arrow, para escribir, leer y consolidar archivos
Parquet;dplyr, incorporado posteriormente para la comprobación
de unicidad global.La fuente de datos se consulta mediante el endpoint SODA3 de Saber 11:
base_url_v3 <- "https://www.datos.gov.co/api/v3/views/kgxf-xxbe/query.json"
La extracción fue configurada con páginas de:
tamano_pagina <- 20000
registros.
Cada página se almacena de forma independiente en:
data/raw/saber11_completo/
utilizando nombres secuenciales del tipo:
saber11_pagina_000001.parquet
saber11_pagina_000002.parquet
...
Esta arquitectura evita mantener la base completa en memoria durante la descarga y permite reanudar el proceso si ocurre una interrupción.
El script exige la presencia de cuatro campos internos de Socrata:
:id:version:created_at:updated_atEl campo :id se utiliza como identificador técnico de
fila para las verificaciones de integridad de la adquisición.
Para cada página se verifica que:
:id exista;Estos controles buscan impedir que una página incompleta, corrupta o repetida sea incorporada silenciosamente a la capa raw.
La función descargar_pagina() permite hasta cinco
intentos por página.
Cuando ocurre un error temporal de conexión, el script captura el
error mediante tryCatch(), informa el intento fallido,
aplica una espera progresiva mediante
Sys.sleep(3 * intento) y vuelve a consultar la misma
página.
Si los cinco intentos fallan, la ejecución se detiene.
Esta estrategia es adecuada para una extracción de varios millones de registros porque distingue entre fallos transitorios de red y fallos persistentes que requieren intervención.
Durante la ejecución ocurrieron fallos temporales en las páginas:
Los mensajes registrados incluyeron:
Timeout of 600 seconds was reachedFailure when receiving data from the peerEn todos estos casos el mecanismo de reintentos permitió recuperar posteriormente la página y continuar la extracción.
Estos eventos no constituyeron pérdida de información porque el script no avanzó a la página siguiente hasta obtener correctamente el bloque solicitado.
La presencia de estos fallos demuestra la utilidad práctica del mecanismo de reintentos.
Cada página recuperada correctamente se escribe inmediatamente
mediante write_parquet().
El diseño:
El contador filas_acumuladas se actualiza después de
cada escritura exitosa.
La extracción final produjo:
| Indicador | Resultado |
|---|---|
| Páginas con datos | 356 |
| Tamaño máximo de página | 20.000 |
| Registros acumulados | 7.109.704 |
| Filas de la última página | 9.704 |
| Página siguiente consultada | 357 |
| Registros en página 357 | 0 |
Las páginas 1 a 355 contienen 20.000 registros y la página 356 contiene 9.704.
La suma es:
\[ 355 \times 20.000 + 9.704 = 7.109.704 \]
La página 357 no devolvió registros, proporcionando una señal adicional de que se alcanzó el final de la fuente paginada.
Durante el desarrollo se reforzó la condición que identifica el final de la fuente:
if (!is.data.frame(bloque) || length(bloque) == 0 || nrow(bloque) == 0) {
cat("La página", pagina, "no contiene registros. Finaliza la descarga.\n")
break
}
La ejecución posterior confirmó:
La página 357 no contiene registros. Finaliza la descarga.
El cambio mejora la robustez del extractor porque permite interpretar correctamente una respuesta vacía como señal de terminación.
El script reconoce archivos previamente descargados.
Antes de reanudar:
:id con la copia local;En la ejecución observada, después de completar 356 páginas, la lógica de reanudación informó:
Reanudando desde la página: 357 | Filas existentes: 7109704
Esto confirma que el sistema reconoció correctamente todo el trabajo almacenado.
El script compara la última página local con la misma página consultada nuevamente en Socrata.
Si los vectores de :id no coinciden, el proceso se
detiene.
La paginación de una fuente remota puede generar inconsistencias si el dataset cambia durante una extracción prolongada.
Esta comprobación busca evitar que una reanudación mezcle estados diferentes de la fuente.
No elimina por sí sola todos los riesgos posibles de mutación concurrente, pero constituye una salvaguarda importante.
Una vez terminada la descarga se vuelven a enumerar los archivos Parquet y se comprueba que los números de página formen exactamente la secuencia:
1, 2, 3, ..., 356
Si existe cualquier salto, el proceso se detiene.
La secuencia completa superó la validación.
No se detectaron páginas faltantes en la capa local.
La validación final vuelve a recorrer los 356 archivos.
Para reducir el uso de memoria se lee únicamente:
col_select = ":id"
En cada archivo se comprueba nuevamente:
:id faltantes;:id vacíos;El control se realiza tanto durante la adquisición como después de la escritura física.
Esto permite detectar problemas que pudieran haberse producido entre la descarga y el almacenamiento.
El script comprueba que todas las páginas intermedias contengan exactamente 20.000 registros.
También verifica que la última página:
Se definió:
filas_esperadas <- 7109704
paginas_esperadas <- ceiling(filas_esperadas / tamano_pagina)
Por tanto:
Ambos valores coincidieron con los archivos almacenados.
El proceso contrasta tres cantidades:
\[ N_{local} = N_{fuente} = N_{esperado} \]
Los resultados fueron:
\[ 7.109.704 = 7.109.704 = 7.109.704 \]
| Fuente del conteo | Registros |
|---|---|
| Archivos Parquet locales | 7.109.704 |
| Conteo actual de Socrata | 7.109.704 |
| Total esperado configurado | 7.109.704 |
La coincidencia exacta constituye evidencia fuerte de completitud cuantitativa.
No equivale por sí sola a una prueba de identidad fila por fila; por
eso se incorporan controles adicionales mediante :id.
El total de la fuente se obtiene mediante una consulta:
SELECT count(*) AS total_registros
El código comprueba que la respuesta sea válida y que
total_registros pueda convertirse a numérico.
Socrata reportó:
7.109.704 registros.
Además del conteo total, el script vuelve a consultar:
Luego compara los vectores de :id obtenidos desde la
fuente con los almacenados localmente.
Ambas comparaciones fueron superadas.
La prueba de extremos complementa la continuidad de páginas, el conteo total, las validaciones de tamaño y los controles de identificadores.
El archivo:
_DESCARGA_COMPLETA.txt
se crea únicamente después de superar las verificaciones finales.
El marcador registra:
Esto evita confundir una descarga interrumpida con una descarga validada.
La consola reportó:
VALIDACIÓN CORRECTA |
Páginas: 356 |
Filas locales: 7109704 |
Filas fuente: 7109704 |
Filas esperadas: 7109704 |
Última página: 9704 |
Columnas esperadas: 55
Este resultado resume la primera capa de validación de la adquisición.
:idLa comprobación inicial garantizaba:
Sin embargo, no descartaba formalmente que un :id de una
página antigua pudiera reaparecer muchas páginas después.
Por esta razón se añadió una validación global.
Los archivos se abrieron como un único dataset mediante
open_dataset().
Se calcularon:
filas_totales = n()
ids_unicos = n_distinct(`:id`)
Resultado:
| Métrica | Resultado |
|---|---|
| Filas totales | 7.109.704 |
:id únicos |
7.109.704 |
| Diferencia | 0 |
La consola confirmó:
UNICIDAD GLOBAL CORRECTA | Filas: 7109704 | IDs únicos: 7109704
No existe evidencia de duplicación técnica de :id en
toda la extracción.
La igualdad:
\[ N_{filas} = N_{:id\ únicos} \]
demuestra que cada fila descargada posee un identificador técnico Socrata diferente.
No demuestra necesariamente que dos filas con contenido sustantivo similar correspondan a entidades académicas diferentes.
Por tanto:
No deben eliminarse registros de la capa raw únicamente porque varias variables sustantivas sean idénticas si sus
:idson diferentes.
Una posible duplicación semántica debe investigarse posteriormente mediante claves propias de Saber 11.
Al finalizar la etapa se cuenta con las siguientes comprobaciones:
:id faltantes dentro de
páginas.:id dentro de
páginas.:id globalmente únicos.En conjunto, estas verificaciones proporcionan evidencia fuerte de que la descarga es completa desde el punto de vista cuantitativo y no presenta duplicación técnica inducida por la estrategia de paginación.
Después de validar las páginas individuales se construyó:
D:/Proyectos_IA/prueba_saber_11/data/raw/saber11_completo.parquet
Antes de consolidar se verificó:
Este último control evita sobrescribir accidentalmente una versión anterior.
Los bloques se abren como un dataset lógico mediante:
dataset_paginas <- open_dataset(
archivos_paginas,
format = "parquet"
)
y posteriormente se escriben en un único archivo mediante:
write_parquet(
dataset_paginas,
archivo_final
)
La consola confirmó:
ARCHIVO CONSOLIDADO CREADO
Arrow permite trabajar con millones de registros sin cargar
simultáneamente todas las páginas en memoria como un único
data.frame.
La inspección final mediante glimpse() reportó:
La dimensión coincide con el total validado durante la extracción.
La inspección final confirma variables correspondientes a:
:id:version:created_at:updated_atperiodoestu_consecutivocole_codigo_icfesdesemp_inglespunt_inglespunt_matematicaspunt_sociales_ciudadanaspunt_c_naturalespunt_lectura_criticapunt_globalEn la inspección del consolidado las variables aparecen inicialmente
como character.
Esto es apropiado en la capa raw porque conserva la representación recibida desde la fuente.
La conversión de puntajes, fechas, categorías o códigos no debe mezclarse con la adquisición.
Esas decisiones corresponden a la auditoría y limpieza posteriores.
La arquitectura del proyecto queda:
01_cargar_json.R
↓
02_auditoria_datos.R
↓
03_limpieza_imputacion.R
↓
04_construccion_panel.R
↓
05_feature_engineering.R
↓
06_dataset_supervisado.R
↓
07_modelado.R
La responsabilidad de 01_cargar_json.R es:
adquirir, almacenar, validar y consolidar la información original sin alterar su contenido sustantivo.
El archivo 01 no debe decidir qué registros eliminar,
cómo imputar, qué categorías homologar, qué puntajes transformar, qué
colegios integrar al panel o qué periodos utilizar en el modelo.
Una caída de conexión no obliga a repetir millones de registros ya almacenados.
Cada bloque válido queda guardado inmediatamente.
La integridad no se evalúa únicamente durante la descarga.
El total local se contrasta contra la fuente Socrata.
Se verifica finalmente la unicidad global de :id.
Se evita reemplazar silenciosamente el consolidado existente.
El marcador final registra el estado de la descarga.
A pesar de los controles realizados, el script no prueba por sí solo:
:id.Estas cuestiones corresponden a
02_auditoria_datos.R.
El resultado más importante de esta fase no es únicamente haber descargado más de siete millones de filas.
Lo relevante es que se construyó una capa raw con evidencia explícita de:
Esto permite que los análisis posteriores partan de una base cuya adquisición puede ser defendida y reproducida.
No se recomienda:
:id como identificador del estudiante;:id como prueba de ausencia
de duplicados sustantivos;La capa raw debe conservarse como evidencia del origen de los datos.
Se comparó el número de filas realmente almacenadas con el total esperado y con un conteo independiente obtenido directamente desde Socrata. Los tres valores fueron exactamente 7.109.704.
Cada página válida se escribió inmediatamente en formato Parquet y el proceso puede reanudarse desde la última página almacenada después de verificar que continúa coincidiendo con la fuente.
El extractor permite hasta cinco intentos por página y utiliza una espera progresiva. Los fallos observados fueron transitorios y las páginas terminaron recuperándose correctamente.
Durante la descarga se verificó el solapamiento entre páginas
consecutivas. Posteriormente se ejecutó una prueba global que confirmó
7.109.704 :id diferentes para 7.109.704
filas.
:id demuestra que no hay
estudiantes repetidos?No. :id es un identificador técnico de fila de Socrata.
La duplicación semántica debe investigarse posteriormente usando
variables propias del dominio.
01_cargar_json.REl proceso puede resumirse mediante:
\[ \boxed{ N_{local} = N_{fuente} = N_{esperado} = 7.109.704 } \]
con:
\[ \boxed{ N_{:id\ únicos} = 7.109.704 } \]
y:
\[ \boxed{ 356\ páginas } \]
La última página contiene:
\[ \boxed{ 9.704\ registros } \]
mientras la página 357 no contiene observaciones.
La ejecución de 01_cargar_json.R produjo una extracción
masiva completa y técnicamente validada de la fuente histórica de Saber
11 utilizada en el proyecto.
La arquitectura de descarga demostró capacidad para tolerar fallos transitorios, reanudar la adquisición, impedir huecos en la secuencia de páginas, verificar la identidad técnica de los registros y contrastar el resultado local con el estado reportado por Socrata.
La comprobación global posterior estableció que las 7.109.704
filas poseen 7.109.704 identificadores :id
diferentes, por lo que no se encontró evidencia de duplicación
técnica global causada por la paginación.
Finalmente, las 356 páginas fueron consolidadas en un único archivo Parquet de 7.109.704 filas y 55 columnas, preservando la información original como capa raw.
En consecuencia:
01_cargar_json.Rpuede considerarse cerrado como etapa de adquisición, validación y consolidación. El archivo resultante constituye una base de origen suficientemente controlada para iniciar02_auditoria_datos.R, donde deben estudiarse la calidad sustantiva, los faltantes, la estructura temporal, los identificadores institucionales y la viabilidad longitudinal de Saber 11.