Junta de Andalucía — Contratos menores
Contratos menores publicados en la Plataforma de Contratación de la Junta de Andalucía. Un dataset CKAN por año (contratacion-menor-plataforma-de-contratacion-andalucia-AAAA), de 2018 a 2026, con un CSV y un JSON en cada uno.
Ficha técnica
- Formato
- CSV (separador «|») y JSON, un dataset CKAN por año
- Registros
- ~769.000
- Cobertura
- 2018 a junio de 2026
- Actualización
- Trimestral
- Licencia
- CC BY 4.0
- Entidad
- Junta de Andalucía
Descripción
La Junta de Andalucía publica en su portal de datos abiertos los contratos menores (artículo 118 de la Ley 9/2017) adjudicados y publicados en la Plataforma de Contratación de la Junta. No hay un conjunto único: cada año es un dataset CKAN distinto, de contratacion-menor-plataforma-de-contratacion-andalucia-2018 a contratacion-menor-plataforma-de-contratacion-andalucia-2026, y cada uno trae el mismo contenido en CSV y en JSON. Los publica el Servicio de Coordinación y Contratación Electrónica de la Consejería de Hacienda, con licencia CC BY 4.0 y actualización trimestral según la ficha.
En octubre de 2026 los nueve ficheros sumaban 768.648 filas: unas 50.500 en 2018 y cerca de 125.000 en 2025. El de 2026 llegaba hasta el 30 de junio; se actualizó por última vez el 24 de julio. Cada fila es un expediente, aunque en 2021 y 2022 hay identificadores repetidos.
Las 23 columnas son las mismas todos los años, pero el formato cambia de un fichero a otro y conviene tratarlos uno a uno:
- De 2018 a 2023 el CSV va comprimido en ZIP; desde 2024, sin comprimir (más de 200 MB en 2024 y 2025). - La codificación es Latin-1 hasta 2025 y UTF-8 en 2026. - Las fechas son ISO con zona horaria («2024-02-20T00:00:00+0100») hasta 2024 y DD/MM/AAAA desde 2025. Hay algunas imposibles (años 201, 1492 o 5384) y los ficheros de 2018 a 2023 incluyen adjudicaciones de años anteriores. - Los importes usan punto decimal, salvo en 2025, que usa coma. El importe sin IVA está en todas las filas; el importe con IVA falta en cerca del 90 % de las de 2022 y 2023, y el valor estimado casi no aparece hasta 2021. - Desde 2023 casi todos los campos vienen rellenos de espacios hasta un ancho fijo, y en 2022 la cabecera lleva un espacio delante de las dos fechas. Hay que recortar nombres y valores. - De 2022 a 2024 casi todos los NIF y nombres de adjudicatario terminan en «;», que es el separador cuando hay varios (en 2021 lo lleva una fila de cada ocho). Desde 2025 los varios adjudicatarios se separan con «; » y sin «;» final.
Las URL de descarga que devuelve la API apuntan a un servidor interno (gdc-pdpopendata-ckan.paas.junta-andalucia.es) que no resuelve desde fuera. La misma ruta funciona con www.juntadeandalucia.es, que es lo que hacen los ejemplos.
Estructura de datos
- ID_EXPEDIENTE — Identificador interno del expediente
- ORGANO_CONTRATACION — Órgano de contratación
- NUM_EXPEDIENTE — Número de expediente
- TITULO / DESCRIPCION — Título y objeto del contrato
- TIPO_CONTRATO — Tipo (Servicios, Suministros, Obras…)
- PROCEDIMIENTO_ADJUDICACION — Siempre MENOR
- DURACION_CONTRATO / DURACION_MEDIDA — Duración y unidad (D, M…)
- LUGAR_EJECUCION_CODIGO / LUGAR_EJECUCION_DENOMINACION — Código NUTS y nombre
- VALOR_ESTIMADO — Valor estimado (EUR)
- IMPORTE_ADJUDICACION_SIN_IVA / IMPORTE_ADJUDICACION_CON_IVA — Importe adjudicado (coma decimal en 2025; con IVA, casi vacío en 2022 y 2023)
- FINANCIACION_EUROPEA / FINANCIADO_POR / TASA_COFINANCIACION — Fondos europeos
- NUM_LICITADORES_PRESENTADOS — Número de ofertas
- NIF_ADJUDICATARIO / ADJUDICATARIO_DENOMINACION — Adjudicatario
- FECHA_ADJUDICACION / FECHA_FORMALIZACION — Fechas (ISO hasta 2024, DD/MM/AAAA desde 2025)
- ESTADO — Estado del expediente (Resuelto, Adjudicado…)
Ejemplos de código
# Un dataset por año; la API devuelve las URL con un host interno,
# así que se cambia por www.juntadeandalucia.es
curl -s "https://www.juntadeandalucia.es/datosabiertos/portal/api/3/action/package_search?q=name:contratacion-menor-plataforma-de-contratacion-andalucia-*&rows=50" \
| python3 -c "
import json, sys
for p in sorted(json.load(sys.stdin)['result']['results'], key=lambda p: p['name']):
for r in p['resources']:
if r['format'] == 'CSV':
print(p['name'][-4:], r['url'].replace('gdc-pdpopendata-ckan.paas.junta-andalucia.es', 'www.juntadeandalucia.es'))
" import io
import zipfile
import pandas as pd
import requests
API = 'https://www.juntadeandalucia.es/datosabiertos/portal/api/3/action'
INTERNAL_HOST = 'gdc-pdpopendata-ckan.paas.junta-andalucia.es'
res = requests.get(f'{API}/package_search', params={
'q': 'name:contratacion-menor-plataforma-de-contratacion-andalucia-*', 'rows': 50,
}).json()
frames = []
for pkg in sorted(res['result']['results'], key=lambda p: p['name']):
year = int(pkg['name'][-4:])
csv = next(r for r in pkg['resources'] if r['format'] == 'CSV')
raw = requests.get(csv['url'].replace(INTERNAL_HOST, 'www.juntadeandalucia.es')).content
# De 2018 a 2023 el CSV viene dentro de un ZIP
if raw[:2] == b'PK':
with zipfile.ZipFile(io.BytesIO(raw)) as z:
raw = z.read(z.namelist()[0])
# Latin-1 hasta 2025, UTF-8 en 2026
try:
text = raw.decode('utf-8')
except UnicodeDecodeError:
text = raw.decode('latin-1')
df = pd.read_csv(io.StringIO(text), sep='|', dtype=str)
# Cabeceras y valores con espacios de relleno
df.columns = df.columns.str.strip()
df = df.apply(lambda col: col.str.strip())
# Importe sin IVA: el único completo todos los años (coma decimal en 2025)
df['importe'] = pd.to_numeric(df['IMPORTE_ADJUDICACION_SIN_IVA'].str.replace(',', '.'), errors='coerce')
# Fechas ISO hasta 2024 y DD/MM/AAAA después: formato explícito para no cruzar día y mes
dates = df['FECHA_ADJUDICACION'].str[:10]
date_format = '%d/%m/%Y' if dates.str.contains('/').any() else '%Y-%m-%d'
df['fecha'] = pd.to_datetime(dates, format=date_format, errors='coerce')
df['dataset'] = year
frames.append(df)
print(f'{year}: {len(df):,} contratos, {df["importe"].sum():,.0f} EUR sin IVA, '
f'adjudicados del {df["fecha"].min():%d/%m/%Y} al {df["fecha"].max():%d/%m/%Y}')
menores = pd.concat(frames, ignore_index=True)
print(f'Total: {len(menores):,} contratos menores')