🇪🇸 Datos Abiertos de Contratación Pública - España

March 30, 2026 · View on GitHub

Dataset completo de contratación pública española: nacional (PLACSP) + datos autonómicos (Andalucía, Asturias, Catalunya, Euskadi, Galicia, Valencia, Madrid) + cruce europeo (TED) + Registro Mercantil (BORME).

📊 Resumen de Datos

FuenteRegistrosPeríodoTamaño
Nacional (PLACSP)8.7M2012-2026780 MB
Andalucía~857K2016-202647 MB
Catalunya20.6M2014-2025~180 MB
🆕 Euskadi704K2005-2026~160 MB
Valencia8.5M2000-2026156 MB
Madrid – Comunidad2.56M2017-202590 MB
Madrid – Ayuntamiento119K2015-2025~40 MB
🆕 Galicia1.7M2007-202636 MB
🆕 Asturias375K2019-202421 MB
TED (España)591K2010-202557 MB
🆕 BORME (Registro Mercantil)9.2M empresas + 17M cargos2009-2026750 MB
🆕 Calidad (indicadores)8.7M contratos × 20 indicadores2012-2026977 MB
TOTAL~44.4M + BORME2000-2026~2.3 GB

📥 Descarga de datos

⚠️ Los ficheros .parquet y .csv de este repo usan Git LFS. Si haces fork o descargas el ZIP del repo, solo obtendrás punteros (~130 bytes), no los datos reales.

👉 Descarga directa (sin LFS) → GitHub Releases

ZIPContenidoTamaño
nacional.zipLicitaciones PLACSP1.34 GB
catalunya.zipDatos Catalunya (contratación, subvenciones, RRHH...)1.06 GB
ted.zipTenders Electronic Daily — España217 MB
valencia.zipDatos Valencia (14 categorías)120 MB
andalucia.zipContratación Junta de Andalucía114 MB
euskadi.zipContratación Euskadi109 MB
comunidad_madrid.zipContratación Comunidad de Madrid~90 MB
madrid_ayuntamiento.zipActividad contractual Ayuntamiento de Madrid~40 MB
galicia.zipContratación pública Xunta de Galicia (CM + LIC)~35 MB
asturias.zipContratación centralizada Principado de Asturias~21 MB
borme.zipRegistro Mercantil — actos mercantiles + cargos (anonimizado)750 MB

Cómo obtener los datos

MétodoInstrucciones
Descarga directa (recomendado)Ir a Releases y descargar los ZIP
Git clone + LFSgit clone + git lfs pull (requiere Git LFS instalado)
ForkTras hacer fork, ejecutar git lfs pull en tu copia, o descargar desde Releases

🇪🇺 TED — Diario Oficial de la UE

Contratos publicados en Tenders Electronic Daily correspondientes a España. Los contratos públicos que superan cierto importe (contratos SARA) deben publicarse obligatoriamente en el DOUE.

ConjuntoRegistrosPeríodoFuente
CSV bulk339K2010-2019data.europa.eu
API v3 eForms252K2020-2025ted.europa.eu/api
Consolidado591K2010-2025

Archivos

ted/
├── ted_module.py                    # Script de descarga TED
├── run_ted_crossvalidation.py       # Cross-validation PLACSP↔TED + matching avanzado
├── diagnostico_missing_ted.py       # Diagnóstico de missing
├── analisis_sector_salud.py         # Deep dive sector salud
├── ted_can_2010_ES.parquet          # 2010 (CSV bulk)
├── ted_can_2011_ES.parquet
├── ...
├── ted_can_2019_ES.parquet          # 2019 (CSV bulk)
├── ted_can_2020_ES_api.parquet      # 2020 (API v3 eForms)
├── ...
├── ted_can_2025_ES_api.parquet      # 2025 (API v3 eForms)
└── ted_es_can.parquet               # Consolidado (591K, 31 MB)

Campos principales (57 columnas)

CategoríaCampos
Identificaciónted_notice_id, notice_type, year
Compradorcae_name, cae_nationalid, buyer_legal_type, buyer_country
Contratocpv_code, type_of_contract, procedure_type
Importesaward_value, total_value, estimated_value
Adjudicaciónwin_name, win_nationalid, win_country, win_size (SME)
Competencianumber_offers, direct_award_justification, award_criterion_type
Duraciónduration_lot, contract_start, contract_completion

🔍 Cross-Validation PLACSP ↔ TED

Pipeline para validar si los contratos SARA españoles se publican efectivamente en el Diario Oficial de la UE.

Resultados

MétricaValor
Contratos SARA identificados442,835
Validados en TED177,892 (40.2%)
Missing257,258
Missing alta confianza202,383

Reglas SARA

Los umbrales de publicación obligatoria en TED no son un importe fijo — varían por bienio, tipo de contrato y tipo de comprador:

BienioObrasServicios (AGE)Servicios (resto)Sectores especiales
2016-20175,225,000€135,000€209,000€418,000€
2018-20195,548,000€144,000€221,000€443,000€
2020-20215,350,000€139,000€214,000€428,000€
2022-20235,382,000€140,000€215,000€431,000€
2024-20255,538,000€143,000€221,000€443,000€

Estrategias de matching

El matching se hace de forma secuencial — cada estrategia actúa solo sobre los registros que las anteriores no encontraron:

#EstrategiaMatches% del total
E1NIF adjudicatario + importe ±10% + año ±143,0639.7%
E2Nº expediente + importe ±10%7,8911.8%
E3NIF del órgano contratante + importe77,81617.6%
E4Lotes agrupados (suma importes mismo órgano+año)31,3657.1%
E5Nombre órgano normalizado + importe17,7574.0%

Hallazgo clave: E3 (NIF del órgano) es la estrategia más potente. TED registra el NIF del comprador; PLACSP, el del adjudicatario. Sin cruzar ambos se pierde el 17.6% de matches.

Validación por año

Año     SARA    Match    %
2016   10,948    2,643  24.1%
2017   17,360    6,532  37.6%
2018   32,605   14,720  45.1%
2019   42,951   14,182  33.0%
2020   40,693    9,214  22.6%  ← COVID + baja cobertura TED
2021   47,971    7,472  15.6%
2022   56,649   22,250  39.3%
2023   60,518   31,829  52.6%
2024   59,114   38,216  64.6%  ← máximo
2025   48,276   26,920  55.8%

Análisis sectorial: Salud

El sector salud representa el 17% de contratos SARA con una tasa de validación del 42.3%. El 38% del missing se explica por patrones de lotes (un anuncio TED = N adjudicaciones individuales en PLACSP). La cobertura real ajustada por lotes es ~54%.

Top órganos missing: Servicio Andaluz de Salud (4,833), FREMAP (2,410), IB-Salut (1,957), ICS (1,316), SERGAS (1,291).

Scripts TED

ScriptDescripción
ted/ted_module.pyDescarga TED: CSV bulk (2010-2019) + API v3 eForms (2020-2025)
ted/run_ted_crossvalidation.pyCross-validation PLACSP↔TED con reglas SARA + matching avanzado (5 estrategias)
ted/diagnostico_missing_ted.pyDiagnóstico de missing: falsos positivos vs gaps reales
ted/analisis_sector_salud.pyDeep dive sector salud: lotes, acuerdos marco, CPV, CCAA

🏢 BORME — Registro Mercantil

Datos del Boletín Oficial del Registro Mercantil parseados desde ~126.000 PDFs (2009-2026). Permite cruzar relaciones societarias con contratación pública para detectar anomalías.

ConjuntoRegistrosContenido
Empresas9.2M filas, 3.3M únicasActos mercantiles: constituciones, disoluciones, fusiones, ampliaciones de capital...
Cargos17M filas, 3.8M personasNombramientos, ceses, revocaciones — con persona hasheada (SHA-256)

⚠️ Los PDFs originales no se redistribuyen porque contienen nombres de personas físicas protegidos por RGPD. Se publica el scraper para descargarlos directamente desde boe.es y los datos derivados anonimizados.

Archivos

borme/
├── data/
│   ├── borme_empresas_pub.parquet     # 9.2M actos mercantiles por empresa
│   └── borme_cargos_pub.parquet       # 17M cargos (persona_hash, no nombre real)
└── scripts/
    ├── borme_scraper.py               # Descarga PDFs desde boe.es
    ├── borme_batch_parser.py          # Extrae actos mercantiles de los PDFs
    ├── borme_validate.py              # Validación del parser
    ├── borme_anonymize.py             # Genera datasets públicos sin datos personales
    └── borme_placsp_match.py          # Cruza BORME × PLACSP → flags de anomalías
``$

### \text{Detector} \text{de} \text{anomal}í\text{as} (\text{BORME}  \times  \text{PLACSP})

| \text{Flag} | \text{Se}ñ\text{al} | \text{Descripci}ó\text{n} |
|------|-------|-------------|
| 1 | \text{Empresa} \text{reci}é\text{n} \text{creada} | \text{Constituci}ó\text{n} < 6 \text{meses} \text{antes} \text{de} \text{adjudicaci}ó\text{n} |
| 2 | \text{Capital} \text{rid}í\text{culo} | \text{Capital} \text{social} < 10\text{K}€ \text{ganando} \text{contratos} > 100\text{K}€ |
| 3 | \text{Administradores} \text{compartidos} | \text{Misma} \text{persona} \text{administrando} \text{empresas} \text{competidoras} |
| 4 | \text{Disoluci}ó\text{n} \text{post}-\text{adjudicaci}ó\text{n} | \text{Disuelta} < 12 \text{meses} \text{despu}é\text{s} \text{de} \text{cobrar} |
| 5 | \text{Adjudicaci}ó\text{n} \text{en} \text{concurso} | \text{Empresa} \text{en} \text{situaci}ó\text{n} \text{concursal} \text{recibiendo} \text{contratos} |

### \text{Pipeline}

$``bash
# 1. Descargar PDFs (~126K, ~6 GB)
python borme/scripts/borme_scraper.py --start 2009-01-01 --output ./borme_pdfs

# 2. Parsear → borme_empresas.parquet + borme_cargos.parquet (PRIVADOS)
python borme/scripts/borme_batch_parser.py --input ./borme_pdfs --workers 8

# 3. Anonimizar → versiones públicas con persona_hash
python borme/scripts/borme_anonymize.py --input ./borme_pdfs --output borme/data

# 4. Detectar anomalías cruzando con PLACSP
python borme/scripts/borme_placsp_match.py --borme ./borme_pdfs --placsp nacional/licitaciones_espana.parquet --output ./anomalias

🆕 Calidad de Datos — 20 Indicadores

Pipeline de calidad que aplica 20 indicadores de validez, consistencia y fiabilidad sobre el dataset nacional (PLACSP), cruzando con TED y BORME.

8,693,891 contratos evaluados | Score medio: 88.3 | Mediana: 89.5

Resultados

Indicador% FalloDescripción
INT-VAL-0122.6%Importe de licitación en formato válido
INT-VAL-0231.8%Importe de adjudicación en formato válido
INT-VAL-0739.2%Fecha de adjudicación válida
INT-VAL-0923.3%Código CPV válido
INT-VAL-1233.4%NIF/CIF adjudicatario válido (checksum)
INT-VAL-141.3%Contrato menor coherente con cuantía (LCSP art. 118)
INT-CONS-011.1%Si adjudicado, nº ofertas ≥ 1
INT-CONS-080.3%Importe adjudicación ≤ licitación (+5%)
INT-CONS-1851.3%Adjudicatario existe en BORME (3.3M empresas)
INT-CONS-2077.5%Contrato SARA publicado en TED (5 estrategias matching)
INT-FIA-010.6%Nº ofertas en rango razonable (P99 por CPV)
INT-FIA-0421.3%Plazo presentación ofertas razonable (0-365 días)
INT-FIA-080.4%PBL no outlier (≤ 50M€)
INT-FIA-090.8%PA plausible por segmento CPV (P1-P99)

Los 6 indicadores restantes (formato numérico, no negativos, NUTS, trazabilidad) dan 0.0% de fallo — checks de sanidad que se aplican pero no revelan problemas.

Los indicadores se basan en el marco de calidad de PPDS, con contribuciones de Jaime Gómez-Obregón (umbrales LCSP) y OIRESCON (benchmarks PBL).

Menores vs Regulares

IndicadorMenores (3.3M)Regulares (5.4M)Diferencia
NIF adjudicatario inválido2.7%52.1%-49.5pp
Sin CPV60.3%0.7%+59.6pp
Sin fecha adjudicación0.8%62.6%-61.9pp
Sin importe licitación53.1%4.1%+49.0pp
Score medio90.886.7+4.1

Archivos

calidad/
├── calidad_licitaciones.py                  # Pipeline (429 líneas)
└── calidad_licitaciones_resultado.parquet   # 8.7M × 70 cols (977 MB)

Uso

# Solo indicadores base (17)
python calidad/calidad_licitaciones.py -i nacional/licitaciones_espana.parquet

# Completo con TED + BORME (20 indicadores)
python calidad/calidad_licitaciones.py -i nacional/licitaciones_espana.parquet \
  --ted ted/crossval_sara_v2.parquet \
  --borme borme_empresas.parquet
import pandas as pd
df = pd.read_parquet('calidad/calidad_licitaciones_resultado.parquet')

# Contratos SARA no publicados en TED
df[df['INT-CONS-20'] == False].groupby('organo_contratante').size().nlargest(10)

# Contratos menores que superan umbral LCSP
df[df['INT-VAL-14'] == False][['expediente', 'organo_contratante', 'importe_adjudicacion']]

# Score medio por órgano
df.groupby('organo_contratante')['score_calidad'].mean().nlargest(20)

🏛️ Nacional - PLACSP

Licitaciones de la Plataforma de Contratación del Sector Público.

ConjuntoRegistrosPeríodo
Licitaciones3.6M2012-actualidad
Agregación CCAA1.7M2016-actualidad
Contratos menores3.3M2018-actualidad
Encargos medios propios14.7K2021-actualidad
Consultas preliminares3.7K2022-actualidad

Archivos

nacional/
├── licitaciones_espana.parquet              # Última versión (641 MB)
└── licitaciones_completo_2012_2026.parquet  # Historial completo (780 MB)

Campos principales (48 columnas)

CategoríaCampos
Identificaciónid, expediente, objeto, url
Órganoorgano_contratante, nif_organo, dir3_organo, ciudad_organo
Tipotipo_contrato, subtipo_code, procedimiento, estado
Importesimporte_sin_iva, importe_con_iva, importe_adjudicacion
Adjudicaciónadjudicatario, nif_adjudicatario, num_ofertas, es_pyme
Clasificacióncpv_principal, cpvs, ubicacion, nuts
Fechasfecha_publicacion, fecha_limite, fecha_adjudicacion

🏴 Catalunya

Datos del portal Transparència Catalunya (Socrata API).

CategoríaRegistrosPeríodo
Subvenciones RAISC9.6M2014-2025
Contratación pública4.3M2014-2025
↳ Contratos regulares1.3M2014-2025
↳ Contratos menores 🆕3.0M2014-2025
Presupuestos3.1M2014-2025
Convenios62K2014-2025
RRHH3.4M2014-2025
Patrimonio112K2020-2025

Archivos

catalunya/
├── contratacion/
│   ├── contractacio_publica.parquet         # 1.3M contratos regulares
│   └── contractacio_menors.parquet          # 3.0M contratos menores 🆕
├── subvenciones/
│   └── raisc_subvenciones.parquet           # 9.6M registros
├── pressupostos/
│   └── pressupostos_*.parquet
├── convenis/
│   └── convenis_*.parquet
├── rrhh/
│   └── rrhh_*.parquet
└── patrimoni/
    └── patrimoni_*.parquet

🆕 Contratos menores Catalunya

Dataset nuevo con 3.024.000 registros de contratos menores del sector público catalán:

  • 43 columnas incluyendo: id, descripcio, pressupostLicitacio, pressupostAdjudicacio, adjudicatariNom, adjudicatariNif, organContractant, fase
  • Incluye histórico completo con todas las actualizaciones de estado de cada contrato
  • Extraído mediante paginación con sub-segmentación automática (72K requests API)
  • Fuente: Transparència Catalunya - Contractació Pública

🆕 Euskadi

Contratación pública del País Vasco / Euskadi, combinando la API REST de KontratazioA con exports XLSX históricos de Open Data Euskadi y portales municipales independientes (Bilbao, Vitoria-Gasteiz). Arquitectura API-first con fallback a XLSX para series históricas.

DatasetRegistrosPeríodoFuente
Contratos sector público664,5452011-2026XLSX anual + JSON 2011-2013
Poderes adjudicadores~919ActualAPI REST KontratazioA
Empresas licitadoras~9,042ActualAPI REST KontratazioA
REVASCON histórico34,5232013-2018CSV/XLSX agregado anual
Bilbao contratos4,8232005-2026Portal municipal Bilbao
Vitoria contratos menoresActualOpen Data Euskadi
Total~704K2005-2026

Archivos

euskadi_parquet/
├── contratos_master.parquet             # 664K contratos (138 MB)
├── poderes_adjudicadores.parquet        # 919 poderes adjudicadores
├── empresas_licitadoras.parquet         # 9K empresas del registro
├── revascon_historico.parquet           # 34K registros 2013-2018
└── bilbao_contratos.parquet            # 4.8K contratos Bilbao

ccaa_euskadi.py                          # Scraper principal v4 (descarga)
consolidar_euskadi_v4.py                 # Consolidación → Parquet
``$

### \text{Campos} \text{principales} (56 \text{columnas} — \text{contratos\_master})

| \text{Categor}í\text{a} | \text{Campos} |
|-----------|--------|
| \text{Identificaci}ó\text{n} | \text{codigo\_contrato}, \text{numero\_expediente}, \text{objeto} |
| Ó\text{rgano} | \text{poder\_adjudicador}, \text{codigo\_organismo}, \text{ambito} |
| \text{Tipo} | \text{tipo\_contrato}, \text{procedimiento}, \text{tramitacion} |
| \text{Importes} | \text{importe\_adjudicacion}, \text{importe\_licitacion}, \text{valor\_estimado} |
| \text{Adjudicaci}ó\text{n} | \text{adjudicatario}, \text{nif\_adjudicatario} |
| \text{Fechas} | \text{fecha\_adjudicacion}, \text{fecha\_formalizacion}, \text{duracion} |
| \text{CPV} | \text{codigo\_cpv} |

### \text{Arquitectura} \text{de} \text{fuentes}

\text{El} \text{scraper} \text{sigue} \text{una} \text{arquitectura} **\text{API}-\text{first}** \text{con} \text{m}ú\text{ltiples} \text{capas} \text{de} \text{fallback}:

**\text{M}ó\text{dulo} \text{A} — \text{API} \text{REST} \text{KontratazioA}** (\text{fuente} \text{principal} \text{para} \text{cat}á\text{logos})
- \text{A1}/\text{A2}: \text{Contratos} \text{y} \text{anuncios} (\text{muestra} 1\text{K} \text{registros} — \text{bulk} \text{inviable}: 655\text{K} \text{items}  \times  10/\text{p}á\text{g} = 65\text{K} \text{peticiones} ~27\text{h})
- \text{A3}: \text{Poderes} \text{adjudicadores} — 919 \text{registros} \text{completos} (92 \text{p}á\text{ginas})
- \text{A4}: \text{Empresas} \text{licitadoras} — 9{,}042 \text{registros} \text{completos} (905 \text{p}á\text{ginas})
- \text{Paginaci}ó\text{n}: $?currentPage=N` (1-based, 10 items/pág fijo)

**Módulo B — XLSX/CSV Históricos** (fuente principal para contratos)
- B1: XLSX anuales 2011-2026 (655K registros) + JSON fallback 2011-2013 (9.5K registros de XLSX vacíos)
- B2: REVASCON agregado 2013-2018 (formato más rico que B1 para ese período)
- B3: Snapshot últimos 90 días (ventana móvil)

**Módulo C — Portales municipales** (datos no centralizados)
- C1: Bilbao — contratos adjudicados 2005-2026 (CSV por año + tipo)
- C2: Vitoria-Gasteiz — contratos menores formalizados

### Notas técnicas

- La API de KontratazioA usa `?currentPage=N` para paginación (no `page`, `_page`, ni HATEOAS). El parámetro `_pageSize` se ignora (fijo a 10).
- Los XLSX de 2011-2013 se publican vacíos (solo cabeceras), pero los JSON del mismo endpoint de Open Data sí contienen los datos completos (9,482 registros combinados).
- El consolidador convierte columnas con listas/dicts a JSON string antes de deduplicar, necesario para los campos anidados de la API (clasificaciones, categorías).

---

## 🍊 Valencia

Datos del portal [Dades Obertes GVA](https://dadesobertes.gva.es) (CKAN API).

| Categoría | Archivos | Registros | Contenido |
|-----------|----------|-----------|-----------|
| Contratación | 13 | 246K | REGCON 2014-2025 + DANA |
| Subvenciones | 52 | 2.2M | Ayudas 2022-2025 + DANA |
| Presupuestos | 4 | 346K | Ejecución 2024-2025 |
| Convenios | 5 | 8K | 2018-2022 |
| Lobbies (REGIA) | 7 | 11K | Único en España 🌟 |
| Empleo | 42 | 888K | ERE/ERTE 2000-2025, DANA |
| Paro | 283 | 2.6M | Estadísticas LABORA |
| Siniestralidad | 10 | 570K | Accidentes 2015-2024 |
| Patrimonio | 3 | 9K | Inmuebles GVA |
| Entidades | 2 | 94K | Locales + Asociaciones |
| Territorio | 1 | 4K | Centros docentes |
| Turismo | 16 | 383K | Hoteles, VUT, campings... |
| Sanidad | 8 | 189K | Mapa sanitario |
| Transporte | 7 | 993K | Bus interurbano GTFS |

### Archivos

valencia/ ├── contratacion/ # 13 archivos, 42 MB ├── subvenciones/ # 52 archivos, 26 MB ├── presupuestos/ # 4 archivos, 7 MB ├── convenios/ # 5 archivos, 2 MB ├── lobbies/ # 7 archivos, 0.4 MB 🌟 REGIA ├── empleo/ # 42 archivos, 13 MB ├── paro/ # 283 archivos, 17 MB ├── siniestralidad/ # 10 archivos, 0.6 MB ├── patrimonio/ # 3 archivos, 0.4 MB ├── entidades/ # 2 archivos, 4 MB ├── territorio/ # 1 archivo, 0.4 MB ├── turismo/ # 16 archivos, 17 MB ├── sanidad/ # 8 archivos, 6 MB └── transporte/ # 7 archivos, 21 MB


### 🌟 Datos únicos de Valencia

- **REGIA**: Registro de lobbies único en España (grupos de interés, actividades de influencia)
- **DANA**: Datasets específicos de la catástrofe (contratos, subvenciones, ERTE)
- **ERE/ERTE histórico**: 25 años de datos (2000-2025)
- **Siniestralidad laboral**: 10 años de accidentes de trabajo

---

## 🆕 Andalucía

Contratación pública de la [Junta de Andalucía](https://www.juntadeandalucia.es/haciendayadministracionpublica/apl/pdc-front-publico/perfiles-licitaciones/buscador-general), incluyendo licitaciones regulares y contratos menores de todos los organismos y empresas públicas andaluzas. Extraído mediante ingeniería inversa del proxy Elasticsearch del portal, con estrategia de subdivisión recursiva en 8 dimensiones para superar el límite de 10K resultados por consulta.

Conteos observados en torno al 2026-03-23 consultando la API pública del portal:

| Tipo | Registros | Cobertura |
|------|-----------|-----------|
| Licitaciones regulares (estándar, sin BRR) | ~80.9K | Operativa |
| Contratos menores (sin BRR) | ~775.7K | Operativa |
| **Total (sin BRR)** | **~856.7K** | **Operativa** |

### Archivos

ccaa_Andalucia/ └── licitaciones_andalucia.parquet # ~857K registros (47 MB, snappy)

scripts/ └── ccaa_andalucia.py # Scraper ES proxy 8D + multi-sort + salida CSV/Parquet


### Campos principales (34 columnas)

| Categoría | Campos |
|-----------|--------|
| Identificación | id_expediente, numero_expediente, titulo |
| Clasificación | tipo_contrato, estado, procedimiento, tramitacion |
| Órgano | perfil_contratante, provincia |
| Importes | importe_licitacion, valor_estimado, importe_adjudicacion |
| Adjudicación | adjudicatario, nif_adjudicatario |
| Fechas | fecha_publicacion, fecha_limite_presentacion |
| Otros | forma_presentacion, clausulas_sociales, clausulas_ambientales |

### Estrategia de descarga

El portal de la Junta de Andalucía usa un proxy frontend que limita a 10.000 resultados por consulta Elasticsearch. Con 850K registros totales, se requirió una estrategia de subdivisión recursiva en **8 dimensiones** + multi-sort para cobertura completa:

1. **codigoProcedimiento**: Estándar vs Menores
2. **tipoContrato.codigo**: 21 tipos (SERV, SUM, OBRA, PRIV...)
3. **estado.codigo**: 14 estados (RES, ADJ, PUB, EVA...)
4. **codigoTipoTramitacion**: 5 valores + null (295K registros sin tramitación)
5. **perfilContratante.codigo**: ~400 organismos
6. **provinciasEjecucion**: 8 provincias + null
7. **formaPresentacion**: 6 valores + null
8. **numeroExpediente (año)**: match por texto "2018"-"2026" + null

Para los chunks que aún superan 10K tras las 8 dimensiones (ej. SYBS03/Servicio Andaluz de Salud con 290K registros), se usa **multi-sort con 12 órdenes** distintas (idExpediente, importeLicitacion, numeroExpediente, titulo, fechaLimitePresentacion, adjudicaciones.importeAdjudicacion — cada una asc/desc) que acceden a ventanas diferentes de 10K registros con 0% de solapamiento.

### Perfiles incluidos (~400)

Todas las consejerías, agencias, hospitales del SAS, universidades, diputaciones provinciales, empresas públicas y fundaciones de la Junta de Andalucía, incluyendo:

- Servicio Andaluz de Salud — SYBS03 (290K contratos, mayor organismo)
- 8 Diputaciones provinciales
- 10 Universidades públicas
- Consejerías (Salud, Educación, Fomento, Economía, etc.)
- Agencias (IDEA, AEPSA, ADE, etc.)

---

## 🏛️ Madrid – Comunidad Autónoma

Contratación pública completa de la [Comunidad de Madrid](https://contratos-publicos.comunidad.madrid), incluyendo todas las consejerías, hospitales, organismos autónomos y empresas públicas. Extraído mediante web scraping del buscador avanzado con resolución del módulo antibot de Drupal.

| Tipo de publicación | Registros | Presupuesto licitación | Importe adjudicación |
|---------------------|-----------|----------------------|---------------------|
| Contratos menores | 2,529,049 | 487M € | 487M € |
| Convocatoria anunciada a licitación | 21,070 | 39,551M € | — |
| Contratos adjudicados sin publicidad | 10,035 | 8,466M € | — |
| Encargos a medios propios | 2,178 | 173M € | — |
| Anuncio de información previa | 1,166 | 327M € | — |
| Consultas preliminares del mercado | 28 | — | — |
| **Total** | **2,563,527** | **49,004M €** | **487M €** |

### Archivos

comunidad_madrid/ ├── contratacion_comunidad_madrid_completo.parquet # Dataset unificado (90 MB, snappy) └── csv_originales/ # 765 CSVs individuales


### Campos principales (18 columnas)

| Categoría | Campos |
|-----------|--------|
| Identificación | Nº Expediente, Referencia, Título del contrato |
| Clasificación | Tipo de Publicación, Estado, Tipo de contrato |
| Entidad | Entidad Adjudicadora |
| Proceso | Procedimiento de adjudicación, Presupuesto de licitación, Nº de ofertas |
| Adjudicación | Resultado, NIF del adjudicatario, Adjudicatario, Importe de adjudicación |
| Incidencias | Importe de las modificaciones, Importe de las prórrogas, Importe de la liquidación |
| Temporal | Fecha del contrato |

### Estrategia de descarga

El portal de la Comunidad de Madrid usa un módulo antibot de Drupal y tiene restricciones complejas en los filtros de búsqueda que requirieron ingeniería inversa:

- **Antibot key**: El JavaScript del portal transforma la clave de autenticación invirtiendo pares de 2 caracteres desde el final. El script replica esta transformación.
- **CAPTCHA matemático**: Cada descarga CSV requiere resolver una operación aritmética (ej. `3 + 8 =`).
- **Contratos menores** (~99% del volumen): El filtro `fecha_hasta` es incompatible con este tipo de publicación, y `fecha_desde` no funciona combinado con `entidad_adjudicadora`. Solución: descargar por **entidad adjudicadora** (125 entidades) sin filtro de fecha.
- **Subdivisión recursiva**: Las entidades con >50K registros (hospitales grandes) se subdividen automáticamente por **rango de presupuesto de licitación**, partiendo rangos por la mitad recursivamente hasta que cada segmento queda por debajo del límite de truncamiento.
- **Otros tipos** (licitaciones, adjudicaciones, etc.): Se descargan por **mes + tipo de publicación** con filtros de fecha, que sí funcionan para estos tipos.

### Entidades incluidas (125)

Todas las consejerías, organismos autónomos, empresas públicas y fundaciones de la CAM, incluyendo:

- 10 Consejerías (Sanidad, Educación, Digitalización, Economía, etc.)
- 30+ Hospitales del SERMAS (Gregorio Marañón, La Paz, 12 de Octubre, Ramón y Cajal, etc.)
- Canal de Isabel II y filiales
- Fundaciones IMDEA (7)
- Fundaciones de investigación biomédica (12)
- Consorcios urbanísticos, agencias y entes públicos

---

## 🏛️ Madrid – Ayuntamiento

Actividad contractual completa del [Ayuntamiento de Madrid](https://datos.madrid.es), unificando 67 ficheros CSV con 12 estructuras distintas en un único dataset normalizado.

| Categoría | Registros | Importe total |
|-----------|-----------|---------------|
| Contratos menores | 68,626 | 407M € |
| Contratos formalizados | 17,991 | 16,606M € |
| Acuerdo marco / sist. dinámico | 24,621 | 2,549M € |
| Prorrogados | 4,441 | 2,967M € |
| Modificados | 1,789 | 718M € |
| Cesiones | 30 | 80M € |
| Resoluciones | 225 | 62M € |
| Penalidades | 483 | 13M € |
| Homologación | 1,047 | 1M € |
| **Total** | **119,253** | **~23,400M €** |

### Archivos

El script `ccaa_madrid_ayuntamiento.py` genera:

### Campos principales (70+ columnas)

| Categoría | Campos |
|-----------|--------|
| Identificación | n_registro_contrato, n_expediente, fuente_fichero, categoria |
| Organización | centro_seccion, organo_contratacion, organismo_contratante |
| Objeto | objeto_contrato, tipo_contrato, subtipo_contrato, codigo_cpv |
| Licitación | importe_licitacion_iva_inc, n_licitadores_participantes, n_lotes |
| Adjudicación | importe_adjudicacion_iva_inc, nif_adjudicatario, razon_social_adjudicatario, pyme |
| Fechas | fecha_adjudicacion, fecha_formalizacion, fecha_inicio, fecha_fin |
| Derivados (A.M.) | n_contrato_derivado, objeto_derivado, fecha_aprobacion_derivado |
| Incidencias | tipo_incidencia, importe_modificacion, importe_prorroga, importe_penalidad |
| Cesiones | adjudicatario_cedente, cesionario, importe_cedido |
| Resoluciones | causas_generales, causas_especificas, fecha_acuerdo_resolucion |
| Homologación | n_expediente_sh, objeto_sh, duracion_procedimiento |

### Estructuras detectadas

El script detecta y unifica automáticamente 12 estructuras de CSV distintas:

| Estructura | Período | Categorías |
|------------|---------|------------|
| A, B, C, D | 2015-2020 | Contratos menores |
| E, F | 2021-2025 | Contratos menores |
| AC_OLD | 2015-2020 | Formalizados, acuerdo marco |
| AC_OLD_MOD | 2015-2020 | Modificados |
| AC_HOMOLOGACION | 2022-2024 | Homologación |
| AC_NEW | 2021-2024 | Todas las categorías |
| AC_2025 | 2025 | Todas las categorías |

### Fuentes

- [Contratos menores](https://datos.madrid.es/portal/site/egob/menuitem.c05c1f754a33a9fbe4b2e4b284f1a5a0/?vgnextoid=9e42c176aab90410VgnVCM1000000b205a0aRCRD) — 12 ficheros (2015-2025)
- [Actividad contractual](https://datos.madrid.es/portal/site/egob/menuitem.c05c1f754a33a9fbe4b2e4b284f1a5a0/?vgnextoid=7449f3b0a4699510VgnVCM1000001d4a900aRCRD) — 55 ficheros (2015-2025)

---

## 🆕 Galicia

Contratación pública completa de la [Xunta de Galicia](https://www.contratosdegalicia.gal) y todos sus organismos dependientes, extraída mediante ingeniería inversa de la API jQuery DataTables del portal. Incluye contratos menores (adjudicación directa, desde 2018) y licitaciones formales (desde 2007) de 418 organismos.

| Tipo | Registros | Período |
|------|-----------|---------|
| Contratos menores | 1,635,407 | 2018-2026 |
| Licitaciones | 50,382 | 2007-2026 |
| **Total** | **1,685,789** | **2007-2026** |

### Archivos

galicia/ ├── contratos_galicia_base.csv # Dataset base de tabla (12 columnas) ├── contratos_galicia_base.parquet # Base en parquet ├── contratos_galicia_detail.sqlite3 # Caché incremental del detalle HTML ├── contratos_galicia.csv # Dataset final mergeado (62 columnas) ├── contratos_galicia.parquet # Dataset final en parquet ├── contratos_galicia_base_progress.json # Checkpoint de organismos completados └── scraper_galicia.py # Pipeline base + detail + merge


### Campos principales

**Base (12 columnas)**: `id`, `objeto`, `importe`, `estado`, `estadoDesc`, `publicado`, `modificado`, `_organismo_id`, `_tipo`, `nif`, `adjudicatario`, `duracion`

**Detalle HTML enriquecido**: el dataset final añade >50 columnas derivadas de la ficha del portal, entre ellas:

- `detail_tipo_tramitacion`
- `detail_tipo_procedimiento`
- `detail_tipo_contrato`
- `detail_presupuesto_base_text` / `detail_presupuesto_base_eur`
- `detail_valor_estimado_text` / `detail_valor_estimado_eur`
- `detail_num_lotes`
- `detail_fecha_difusion`
- `detail_organo`
- `detail_correo_electronico`
- `detail_cpv_codes`
- `detail_nuts_codes`
- `detail_documentos_count`
- `detail_adjudicaciones_count`
- `detail_status`, `detail_attempts`, `detail_last_error`

Además, la caché SQLite puede guardar comprimidos los `pairs` y `tables` crudos de cada ficha HTML para no perder información aunque no todo se aplane a columnas.

### Estrategia de descarga

El portal usa jQuery DataTables con server-side processing y dos endpoints separados:

- **Licitaciones**: `/api/v1/organismos/{id}/licitaciones/table` — paginación estándar, sin restricciones temporales
- **Contratos menores**: `/api/v1/organismos/{id}/contratosmenores/table` — requiere header `Referer` dinámico por organismo y rechaza rangos de fecha >3 meses

**Discovery automático**: El scraper prueba IDs de organismo 1–2000 contra ambos endpoints (licitaciones en paralelo, CM secuencial por la restricción del `Referer`) para descubrir los organismos activos.

**Barrido temporal CM**: Ventanas de 3 meses desde la fecha actual hasta 2000-01-01. El servidor reporta `recordsTotal` global (ignorando el filtro de fecha), pero los datos devueltos sí están filtrados. Deduplicación por `(id, _tipo)` para eliminar solapamientos entre ventanas.

**Detalle HTML real**: El portal no expone un endpoint JSON útil para la ficha; los campos adicionales salen de `POST /licitacion`. El scraper hace un segundo paso de enriquecimiento HTML para `LIC` y `CM`.

**Pipeline incremental y reanudable**:

- `all`: ejecuta `base -> detail -> merge`
- `base`: reconstruye solo el dataset base de tabla
- `detail`: enriquece HTML sobre el base ya descargado
- `merge`: junta base + detalle en el dataset final

El progreso base se guarda por organismo en `contratos_galicia_base_progress.json` y el detalle se cachea en `contratos_galicia_detail.sqlite3`, de forma que si la ejecución se corta o el portal devuelve `403/429`, se puede retomar con `--resume`.

**Salida reproducible**: otra persona puede rehacer la descarga completa ejecutando el mismo pipeline y obteniendo base, detalle cacheado y merge final.

### Validación reciente

En una validación real sobre un organismo mixto (`33`), el scraper antiguo seguía sacando `152` filas y `12` columnas. El pipeline nuevo mantiene las `152` filas, genera un base estable de `12` columnas y un dataset final enriquecido de `62` columnas, con `152/152` fichas HTML resueltas y una segunda pasada `detail --resume` que no rehace trabajo (`0` procesados).

Este diseño está pensado para ejecutar el backfill histórico completo sin depender de memoria RAM ni de una corrida monolítica: si el portal corta la sesión o devuelve `403/429`, basta con relanzar la fase correspondiente con `--resume`.

---

## 🆕 Asturias

Contratación centralizada del [Principado de Asturias](https://sede.asturias.es/), incluyendo contratos menores, servicios, obras y suministros de todos los organismos y entes públicos del Principado.

| Métrica | Valor |
|---------|-------|
| Registros | 375,380 |
| Período | 2019-2024 |
| Columnas | 99 |
| Tamaño | 21 MB |

### Archivos

ccaa_asturias/ └── asturias_contracts_ALL_YEARS.parquet # 375K registros (21 MB, snappy)


### Campos principales (99 columnas)

| Categoría | Campos |
|-----------|--------|
| Identificación | Nº INSCRIPCION, Nº EXPEDIENTE ORGANO, OBJETO |
| Clasificación | CLASIFICACION GENERAL, CARACTERISTICAS CONTRATO, REGULACION |
| Órgano | ENTE CONTRATANTE, ORGANO CONTRATANTE |
| Importes | PRESUPUESTO, IMP. ADJ. (CON IVA), IMP. ADJ. IMPUESTO, IMP. ADJ. LOTE |
| Proceso | PROC. ADJUDICACION, FORMA ADJUDICACION, T. TRAMITACION |
| Adjudicación | CONTRATISTAS, NIF/CIF CONTRATISTA, RAZON SOCIAL CONTRATISTA, ADJUDICADOS A PYMES |
| CPV | CODIGO CPV |
| Fechas | F. DE ALTA, F. ADJ., F. FORMALIZACION, F. FIN EJECUCION |
| Publicación | F. BOPA, F. BOE, F. DOUE |
| Fondos europeos | CONTRATOS FINANCIADOS CON FONDOS EUROPEOS, TIPO DE FONDO EUROPEO |
| Estrategia | CONT. ESTRAT. C. SOCIALES, C. MEDIOAMBIENTALES, C. DE I+D |
| Recursos | OBJETO DE RECURSO ESPECIAL EN MATERIA DE CONTRATACION |

---

## 📥 Uso

```python
import pandas as pd

# Nacional - PLACSP
df_nacional = pd.read_parquet('nacional/licitaciones_espana.parquet')

# TED - España (consolidado)
df_ted = pd.read_parquet('ted/ted_es_can.parquet')

# Andalucía - Contratación completa
df_and = pd.read_parquet('ccaa_Andalucia/licitaciones_andalucia.parquet')

# Euskadi - Contratos sector público
df_eus = pd.read_parquet('euskadi_parquet/contratos_master.parquet')

# Euskadi - Poderes adjudicadores
df_poderes = pd.read_parquet('euskadi_parquet/poderes_adjudicadores.parquet')

# Euskadi - Empresas licitadoras
df_empresas = pd.read_parquet('euskadi_parquet/empresas_licitadoras.parquet')

# Comunidad de Madrid - Contratación completa
df_cam = pd.read_parquet('comunidad_madrid/contratacion_comunidad_madrid_completo.parquet')

# Madrid Ayuntamiento - Actividad contractual
df_madrid = pd.read_parquet('madrid/actividad_contractual_madrid_completo.parquet')

# Catalunya - Contratos menores
df_cat_menors = pd.read_parquet('catalunya/contratacion/contractacio_menors.parquet')

# Catalunya - Subvenciones
df_cat_subv = pd.read_parquet('catalunya/subvenciones/raisc_subvenciones.parquet')

# Valencia - Contratación
df_val = pd.read_parquet('valencia/contratacion/')

# Valencia - Lobbies REGIA
df_lobbies = pd.read_parquet('valencia/lobbies/')

# BORME - Actos mercantiles (anonimizado)
df_borme = pd.read_parquet('borme/data/borme_empresas_pub.parquet')

# BORME - Cargos con persona hasheada
df_cargos = pd.read_parquet('borme/data/borme_cargos_pub.parquet')

# Galicia - Contratación completa (CM + LIC)
df_gal = pd.read_parquet('galicia/contratos_galicia.parquet')

# Asturias - Contratación centralizada
df_ast = pd.read_parquet('ccaa_asturias/asturias_contracts_ALL_YEARS.parquet')

Ejemplos de análisis

# Top adjudicatarios nacional
df_nacional.groupby('adjudicatario')['importe_sin_iva'].sum().nlargest(10)

# Contratos España publicados en TED por año
df_ted.groupby('year').size().plot(kind='bar', title='Contratos TED España')

# Andalucía: contratos menores por perfil contratante
and_menores = df_and[df_and['procedimiento'] == 'Contrato menor']
and_menores['perfil_contratante'].value_counts().head(20)

# Euskadi: gasto anual por tipo de contrato
df_eus.groupby(['anio', 'tipo_contrato'])['importe_adjudicacion'].sum().unstack().plot()

# Euskadi: top poderes adjudicadores por volumen
df_eus.groupby('poder_adjudicador')['importe_adjudicacion'].sum().nlargest(20)

# Euskadi: empresas más activas en el Registro de Licitadores
df_empresas['officialname'].value_counts().head(10)

# Comunidad de Madrid: contratos menores por hospital
cam_menores = df_cam[df_cam['Tipo de Publicación'] == 'Contratos menores']
cam_menores['Entidad Adjudicadora'].value_counts().head(20)

# Ayuntamiento Madrid: gasto por categoría y año
df_madrid.groupby(['categoria', 'anio'])['importe_adjudicacion_iva_inc'].sum().unstack(0).plot()

# Contratos SARA no publicados en TED
df_sara = pd.read_parquet('ted/crossval_sara_v2.parquet')
missing = df_sara[df_sara['_ted_missing']]
missing.groupby('organo_contratante').size().nlargest(10)

# Contratos menores Catalunya por órgano
df_cat_menors.groupby('organContractant')['pressupostAdjudicacio'].sum().nlargest(10)

# Evolución ERE/ERTE Valencia (2000-2025)
df_erte = pd.read_parquet('valencia/empleo/')
df_erte.groupby('año')['expedientes'].sum().plot()

# BORME: constituciones por año
df_borme = pd.read_parquet('borme/data/borme_empresas_pub.parquet')
constit = df_borme[df_borme['actos'].str.contains('Constitución', na=False)]
constit.groupby(constit['fecha_borme'].dt.year).size().plot(title='Constituciones/año')

# BORME: administradores compartidos entre empresas
df_cargos = pd.read_parquet('borme/data/borme_cargos_pub.parquet')
nombramientos = df_cargos[df_cargos['tipo_acto'] == 'nombramiento']
multi = nombramientos.groupby('persona_hash')['empresa_norm'].nunique()
print(f"Admins en >1 empresa: {(multi > 1).sum():,}")

# Galicia: top 10 adjudicatarios por importe (contratos menores)
df_gal_cm = df_gal[df_gal['_tipo'] == 'CM']
df_gal_cm.groupby('adjudicatario')['importe'].sum().nlargest(10)

# Galicia: evolución del gasto en contratos menores por año
df_gal_cm['año'] = df_gal_cm['publicado'].dt.year
df_gal_cm.groupby('año')['importe'].sum().plot(kind='bar', title='Contratos menores Galicia')

# Galicia: concentración — adjudicatarios que acumulan el 50% del gasto
top = df_gal_cm.groupby('nif')['importe'].sum().sort_values(ascending=False)
n_50 = (top.cumsum() / top.sum() <= 0.5).sum() + 1
print(f"{n_50} adjudicatarios concentran el 50% del gasto en CM Galicia")

# Asturias: gasto anual por tipo de contrato
df_ast = pd.read_parquet('ccaa_asturias/asturias_contracts_ALL_YEARS.parquet')
df_ast.groupby(['year', 'CARACTERISTICAS CONTRATO'])['IMP. ADJ. (CON IVA)'].sum().unstack().plot()

# Asturias: top entes contratantes por volumen
df_ast.groupby('ENTE CONTRATANTE')['IMP. ADJ. (CON IVA)'].sum().nlargest(10)

# Asturias: contratos menores por órgano
ast_menores = df_ast[df_ast['CLASIFICACION GENERAL'] == 'MENOR']
ast_menores['ORGANO CONTRATANTE'].value_counts().head(20)

🔧 Scripts

ScriptFuenteDescripción
nacional/licitaciones.pyPLACSPExtrae datos nacionales de ATOM/XML
scripts/ccaa_andalucia.pyJunta de AndalucíaScraper ES proxy con subdivisión 8D + multi-sort 12x + salida reproducible
ccaa_euskadi.pyKontratazioA + Open Data EuskadiScraper v4: API REST + XLSX anuales + portales municipales
consolidar_euskadi_v4.pyConsolida JSON/XLSX/CSV → 5 Parquets normalizados
descarga_contratacion_comunidad_madrid_v1.pycontratos-publicos.comunidad.madridWeb scraping con antibot bypass + subdivisión recursiva por importe
ccaa_madrid_ayuntamiento.pydatos.madrid.esDescarga y unifica 67 CSVs (9 categorías, 12 estructuras)
scripts/ccaa_cataluna_contratosmenores.pySocrataDescarga contratos menores Catalunya
galicia/scraper_galicia.pycontratosdegalicia.galPipeline base + detalle HTML + merge, con discovery automático, barrido CM 3 meses, caché SQLite y --resume
ccaa_asturias.pyPrincipado de AsturiasDescarga contratación centralizada Asturias
scripts/ccaa_catalunya.pySocrataDescarga datos Catalunya
scripts/ccaa_valencia.pyCKANDescarga datos Valencia
ted/ted_module.pyTEDDescarga CSV bulk + API v3 eForms
ted/run_ted_crossvalidation.pyCross-validation PLACSP↔TED + matching avanzado (5 estrategias)
ted/diagnostico_missing_ted.pyDiagnóstico de missing
ted/analisis_sector_salud.pyDeep dive sector salud
borme/scripts/borme_scraper.pyBOE/BORMEDescarga ~126K PDFs del Registro Mercantil
borme/scripts/borme_batch_parser.pyParser de actos mercantiles (constituciones, cargos...)
borme/scripts/borme_anonymize.pyGenera datasets públicos sin datos personales
`borme/scripts/borme_placsp_match.py$\text{Detector} \text{de} \text{anomal}í\text{as} \text{BORME} \times \text{PLACSP} (5 \text{flags})
$calidad/calidad_licitaciones.py`20 indicadores de calidad sobre PLACSP + TED + BORME

🔄 Actualización

FuenteFrecuencia
PLACSPMensual
TEDTrimestral (API) / Anual (CSV bulk)
AndalucíaTrimestral (re-ejecutar script)
EuskadiTrimestral (re-ejecutar ccaa_euskadi.py + consolidar)
Madrid – ComunidadTrimestral (re-ejecutar script)
Madrid – AyuntamientoAnual (nuevos CSVs por año)
CatalunyaVariable (depende del dataset)
ValenciaDiaria/Mensual (depende del dataset)
GaliciaTrimestral (re-ejecutar scraper, ~8h)
AsturiasAnual (nuevos datasets por año)
BORMETrimestral (re-ejecutar scraper + parser + anonymize)

📋 Requisitos

pip install pandas pyarrow requests beautifulsoup4 pdfplumber python-dateutil

📄 Licencia

Datos públicos del Gobierno de España, Unión Europea y CCAA.


🔗 Fuentes

PortalURL
PLACSPhttps://contrataciondelsectorpublico.gob.es/
TEDhttps://ted.europa.eu/
TED API v3https://ted.europa.eu/api/docs/
TED CSV Bulkhttps://data.europa.eu/data/datasets/ted-csv
Andalucíahttps://www.juntadeandalucia.es/contratacion/
Euskadi — KontratazioAhttps://www.contratacion.euskadi.eus/
Euskadi — Open Datahttps://opendata.euskadi.eus/
Euskadi — API RESThttps://api.euskadi.eus/procurements/
Madrid – Comunidadhttps://contratos-publicos.comunidad.madrid/
Madrid – Ayuntamientohttps://datos.madrid.es/
Catalunyahttps://analisi.transparenciacatalunya.cat/
Valenciahttps://dadesobertes.gva.es/
Galiciahttps://www.contratosdegalicia.gal/
Asturiashttps://sede.asturias.es/
BORMEhttps://www.boe.es/diario_borme/
BQuant Financehttps://bquantfinance.com

📈 Próximas CCAA

  • Euskadi ✅
  • Andalucía ✅
  • Madrid ✅
  • Galicia ✅
  • Asturias ✅
  • Castilla y León

⭐ Si te resulta útil, dale una estrella al repo

@Gsnchez | BQuant Finance