CONSULTAS DE POWER BI — DATOS ABIERTOS DE LA CASA DE JUSTICIA DE BARRANCAS Versión 2.4 — 29/09/2026 · Datos agregados, sin datos personales · Ley 1712 de 2014 · Licencia CC BY 4.0 Fuente: https://www.casadejusticiabarrancas.gov.co/api/datos-abiertos CÓMO USARLAS (Power BI Desktop, gratuito; no requiere credenciales) 1. Archivo → Opciones y configuración → Opciones → Carga de datos → Borrar caché. 2. Inicio → Transformar datos → Editor de Power Query. 3. Para cada bloque: Nueva consulta → Consulta en blanco → Editor avanzado → borrar, pegar el código y renombrar la consulta con el nombre exacto del bloque (con tildes y ñ). Crearlas en orden. 4. Si pide credenciales: Anónimo. Si pide nivel de privacidad: Público. 5. Archivo → Opciones y configuración → Opciones → ARCHIVO ACTUAL → Carga de datos → «Carga de tablas en paralelo»: marcar «Uno (deshabilitar la carga en paralelo)». Evita superar 60 consultas por minuto (error 429). 6. Al terminar: desmarcar «Habilitar carga» en ApiDatosAbiertos, FilasConReservados, Años y Periodos. Luego Inicio → Cerrar y aplicar. PRUEBA OPCIONAL DEL BLOQUE 1: al pegarlo aparece «Escribir parámetros». En «ruta» escriba personas (el nombre de la ruta, no el del parámetro), en «añoConsulta» 2026 y deje «mesConsulta» vacío. Invocar debe mostrar total_registros. Después elimine la consulta de prueba. REGLA DE LECTURA: cuando «reservado» es verdadero, «total» va vacío: significa «menos de 5 casos» (se protege a las personas). Nunca debe mostrarse como cero. Las personas atendidas no se suman entre años. ==================================================================================================== Bloque 1. ApiDatosAbiertos (función) ==================================================================================================== (ruta as text, optional añoConsulta as nullable number, optional mesConsulta as nullable number) as record => let // Rutas que existen en la API. Si se escribe otra cosa, el error lo dice en español. RutasValidas = {"personas", "atenciones", "citas", "vif", "demografia", "visitas", "servicios", "entidades"}, RutaLimpia = Text.Lower(Text.Trim(ruta)), Ruta = if List.Contains(RutasValidas, RutaLimpia) then RutaLimpia else error "La ruta «" & ruta & "» no existe. En el cuadro «ruta» escriba una de estas: " & Text.Combine(RutasValidas, ", ") & ".", // «anio» y «mes» son los nombres que ya usa la dirección pública de la API. Los parámetros de esta // función se llaman distinto a propósito: Power Query falla si un parámetro y un campo comparten nombre. Consulta = Record.Combine({ if añoConsulta = null then [] else [anio = Number.ToText(añoConsulta)], if mesConsulta = null then [] else [mes = Number.ToText(mesConsulta)] }), // Pausa de 1,2 s por llamada: respeta el límite de 60 consultas por minuto de la API. // IsRetry = true: pide siempre la respuesta fresca, nunca una copia guardada en caché. Respuesta = Function.InvokeAfter( () => Web.Contents( "https://www.casadejusticiabarrancas.gov.co/api/datos-abiertos/", [RelativePath = Ruta, Query = Consulta, IsRetry = true] ), #duration(0, 0, 0, 1.2) ) in Json.Document(Respuesta) ==================================================================================================== Bloque 2. FilasConReservados (función) ==================================================================================================== (respuesta as record, columnas as list) as table => let Columnas = columnas & {"total", "reservado", "casos"}, Publicadas = respuesta[value], Reservadas = if Record.HasFields(respuesta, "reservados") then respuesta[reservados] else error "La API respondió sin «reservados»: es una copia vieja guardada en caché. " & "Borre la caché (Archivo → Opciones → Carga de datos) y actualice.", Visibles = List.Transform(Publicadas, each _ & [reservado = false, casos = Text.From(_[total])]), Ocultas = List.Transform( Reservadas, each Record.RemoveFields(_, {"rango"}, MissingField.Ignore) & [total = null, reservado = true, casos = "menos de 5"] ) in Table.FromRecords( List.Transform(Visibles & Ocultas, each Record.SelectFields(_, Columnas, MissingField.UseNull)), Columnas ) ==================================================================================================== Bloque 3. Años ==================================================================================================== let Años = {2025..Date.Year(DateTime.LocalNow())} in Años ==================================================================================================== Bloque 4. Periodos (año y mes, hasta el mes en curso) ==================================================================================================== let Hoy = DateTime.Date(DateTime.LocalNow()), Pares = List.Combine(List.Transform(Años, (a) => List.Transform( {1..(if a = Date.Year(Hoy) then Date.Month(Hoy) else 12)}, (m) => [año = a, mes = m, fecha = #date(a, m, 1)] ))), Tabla = Table.FromRecords(Pares), Tipos = Table.TransformColumnTypes(Tabla, {{"año", Int64.Type}, {"mes", Int64.Type}, {"fecha", type date}}) in Tipos ==================================================================================================== Bloque 5. Resumen_Anual ==================================================================================================== let Filas = List.Transform(Años, (a) => let Citas = ApiDatosAbiertos("citas", a), // «No asistieron» del año: la cifra si se publica; vacío si fueron menos de 5 (reservado); // 0 si no hubo ninguna (el estado no aparece ni publicado ni reservado). EsNoAsistio = (r) => Text.Lower(Record.FieldOrDefault(r, "estado", "")) = "no_asistio", Publicado = List.Select(Citas[value], EsNoAsistio), Reservado = List.Select(Citas[reservados], EsNoAsistio), NoAsistio = if not List.IsEmpty(Publicado) then List.Sum(List.Transform(Publicado, each [total])) else if not List.IsEmpty(Reservado) then null else 0 in [ año = a, #"personas atendidas" = ApiDatosAbiertos("personas", a)[total_registros], #"citas atendidas" = ApiDatosAbiertos("atenciones", a)[total_registros], #"no asistieron" = NoAsistio, #"citas totales" = Citas[total_registros], #"casos VIF" = ApiDatosAbiertos("vif", a)[total_registros] ]), Tabla = Table.FromRecords(Filas), Tipos = Table.TransformColumnTypes(Tabla, { {"año", Int64.Type}, {"personas atendidas", Int64.Type}, {"citas atendidas", Int64.Type}, {"no asistieron", Int64.Type}, {"citas totales", Int64.Type}, {"casos VIF", Int64.Type}}) in Tipos ==================================================================================================== Bloque 6. Citas_Mensual (tarda unos 30 s: una consulta por mes) ==================================================================================================== let NombresEstado = [ atendida = "Atendida", no_asistio = "No asistió", cancelada = "Cancelada", programada = "Programada", confirmada = "Confirmada", en_atencion = "En atención", reagendada = "Reagendada", reprogramada = "Reprogramada" ], Filas = Table.AddColumn(Periodos, "datos", each FilasConReservados(ApiDatosAbiertos("citas", [año], [mes]), {"estado"})), Expandida = Table.ExpandTableColumn(Filas, "datos", {"estado", "total", "reservado", "casos"}), Estados = Table.TransformColumns(Expandida, {{"estado", each if _ = null then null else Record.FieldOrDefault(NombresEstado, Text.Lower(_), _), type text}}), Tipos = Table.TransformColumnTypes(Estados, {{"total", Int64.Type}, {"reservado", type logical}, {"casos", type text}}) in Tipos ==================================================================================================== Bloque 7. Citas_Atendidas_Mensual (por tipo de atención; tarda unos 30 s) ==================================================================================================== let Filas = Table.AddColumn(Periodos, "datos", each FilasConReservados(ApiDatosAbiertos("atenciones", [año], [mes]), {"tipo_atencion"})), Expandida = Table.ExpandTableColumn(Filas, "datos", {"tipo_atencion", "total", "reservado", "casos"}), Nombres = Table.RenameColumns(Expandida, {{"tipo_atencion", "tipo de atención"}}), Tipos = Table.TransformColumnTypes(Nombres, {{"total", Int64.Type}, {"reservado", type logical}, {"casos", type text}}) in Tipos ==================================================================================================== Bloque 8. VIF_Mensual ==================================================================================================== let Filas = List.Combine(List.Transform(Años, (a) => Table.ToRecords(FilasConReservados(ApiDatosAbiertos("vif", a), {"periodo", "estado"})))), Tabla = Table.FromRecords(Filas, {"periodo", "estado", "total", "reservado", "casos"}, MissingField.UseNull), Estados = Table.TransformColumns(Tabla, {{"estado", each if _ = null then "sin detalle (protegido)" else _, type text}}), ConFecha = Table.AddColumn(Estados, "fecha", each #date(Number.From(Text.Start([periodo], 4)), Number.From(Text.End([periodo], 2)), 1), type date), // «año» une esta tabla con Resumen_Anual: el filtro de año del tablero también filtra VIF. ConAño = Table.AddColumn(ConFecha, "año", each Number.From(Text.Start([periodo], 4)), Int64.Type), Tipos = Table.TransformColumnTypes(ConAño, { {"periodo", type text}, {"total", Int64.Type}, {"reservado", type logical}, {"casos", type text}}) in Tipos ==================================================================================================== Bloque 9. Demografía (por año) ==================================================================================================== let NombresDimension = [ sexo = "Sexo", zona = "Zona", etnia = "Etnia o comunidad", estrato = "Estrato", grupo_etario = "Grupo de edad", nivel_educativo = "Nivel educativo", municipio = "Municipio de origen" ], Filas = List.Combine(List.Transform(Años, (a) => List.Transform( Table.ToRecords(FilasConReservados(ApiDatosAbiertos("demografia", a), {"dimension", "categoria"})), each _ & [año = a]))), Tabla = Table.FromRecords(Filas, {"año", "dimension", "categoria", "total", "reservado", "casos"}, MissingField.UseNull), Dimensiones = Table.TransformColumns(Tabla, {{"dimension", each if _ = null then null else Record.FieldOrDefault(NombresDimension, _, _), type text}}), Nombres = Table.RenameColumns(Dimensiones, {{"dimension", "dimensión"}, {"categoria", "categoría"}}), Tipos = Table.TransformColumnTypes(Nombres, { {"año", Int64.Type}, {"total", Int64.Type}, {"reservado", type logical}, {"casos", type text}}) in Tipos ==================================================================================================== MEDIDAS DAX (Inicio → Nueva medida) ==================================================================================================== -- Las medidas llevan «Total» porque no pueden llamarse igual que una columna de la misma tabla. -- Las personas distintas NO se suman entre años: la medida solo responde con un año seleccionado. Total personas atendidas = IF ( HASONEVALUE ( Resumen_Anual[año] ), SUM ( Resumen_Anual[personas atendidas] ) ) Total citas atendidas = SUM ( Resumen_Anual[citas atendidas] ) Total casos VIF = SUM ( Resumen_Anual[casos VIF] ) -- Con los totales del año (los meses con menos de 5 inasistencias vienen vacíos en Citas_Mensual). Inasistencia % = VAR NoAsistio = SUM ( Resumen_Anual[no asistieron] ) VAR Atendidas = SUM ( Resumen_Anual[citas atendidas] ) RETURN DIVIDE ( NoAsistio, NoAsistio + Atendidas ) Meses VIF con menos de 5 casos = CALCULATE ( DISTINCTCOUNT ( VIF_Mensual[periodo] ), VIF_Mensual[reservado] = TRUE () )