Cómo validar ficheros CSV, JSON y Excel
Guía práctica para validar ficheros planos — por qué las hojas de cálculo se rompen de formas que una base de datos nunca conoce, las seis comprobaciones que lo detectan y cómo ejecutar contra un CSV el mismo contrato de datos que ejecutas contra tu almacén.
· 11 min read
Para validar un fichero CSV, JSON o Excel, cárgalo en algo que hable SQL y ejecuta después las mismas comprobaciones que harías contra una tabla del almacén: nulos en columnas obligatorias, claves duplicadas, valores fuera de un conjunto permitido, números fuera de un rango plausible, cadenas mal formadas y filas obsoletas. La diferencia está en lo que viene *antes* de las comprobaciones. Una columna de base de datos tiene un tipo declarado y rechaza inserciones; una columna de hoja de cálculo tiene lo que escribió la última persona. La mayoría de los incidentes con ficheros planos son problemas de parseo y de tipado, no incumplimientos de reglas, así que el parseo es donde empieza de verdad la validación.
Por qué los ficheros planos se rompen de otra manera
Una tabla de almacén ya ha rechazado los peores datos antes de que los veas. Un fichero no ha rechazado nada.
No hay esquema, solo una suposición. Todo lector de CSV infiere los tipos a partir de las primeras N filas. Una columna que contiene 1, 2, 3 durante diez mil filas y N/A en la diez mil uno es una columna de enteros hasta que de repente deja de serlo. Cambia el tamaño de la muestra y el mismo fichero se parsea de otro modo.
Excel reescribe tus datos sin avisar. Los ceros iniciales desaparecen de códigos postales y códigos de producto, los identificadores largos se convierten en notación científica (1.23457E+14) y todo lo que tenga forma de fecha se convierte en una. El problema es lo bastante conocido como para que el comité que nombra los genes humanos renombrara varios genes porque Excel no paraba de convertir símbolos como SEPT2 en fechas. Si un fichero ha pasado por Excel, da por hecho que alguna columna se reformateó por el camino.
Los delimitadores y el entrecomillado también son una suposición. Una exportación delimitada por punto y coma desde una configuración regional europea, parseada como delimitada por comas, da una única columna gigante. Una comilla sin escapar dentro de un campo desplaza en uno todas las columnas siguientes, y solo en algunas filas, lo cual es peor que fallar directamente.
La codificación no se declara. Un fichero escrito en Windows-1252 y leído como UTF-8 convierte é en é. No da error; los datos simplemente quedan mal en silencio.
La cabecera puede no estar en la fila 1. Las exportaciones de herramientas de reporting suelen empezar con una fila de título, una fila en blanco y una línea del tipo «Generado el…» antes de la cabecera real.
Nada de esto es un incumplimiento de reglas. Todo ello produce un fichero que se parsea «con éxito» y da basura, y por eso un flujo de trabajo con ficheros planos necesita un paso de vista previa y confirmación antes de que se ejecute ninguna regla. Acierta con el parseo y el trabajo pasa a ser idéntico al de validar una tabla del almacén, que es lo que cubre al completo la guía completa para validar datos.
Acierta primero con el parseo
Antes de escribir una sola comprobación, confirma cuatro cosas:
- Delimitador y carácter de entrecomillado. La coma, el punto y coma, la barra vertical y el tabulador son todos habituales. Fíjate en la vista previa parseada, no en el texto en bruto.
- Fila de cabecera. ¿La primera columna se llama
order_ido «Exportación de ventas — T3»? - Tipos inferidos. Este es el paso que la gente se salta. Si una columna de identificadores ha vuelto como número, ya has perdido los ceros iniciales.
- Recuento de filas. Si un fichero de 50.000 filas se previsualiza con 3 filas, el entrecomillado está roto.
Catalyst convierte esto en un paso explícito: subes el fichero, lo parsea con DuckDB y muestra las columnas resultantes, los tipos inferidos y una vista previa de filas, y tú ajustas el delimitador, el carácter de entrecomillado y la configuración de cabecera hasta que la vista previa sea correcta. Solo entonces se convierte en un dataset. Para .xls y .xlsx eliges además la hoja, porque si no, un libro con pestañas Data, Pivot y Notes tomará por defecto la que resulte ser la primera.
Un apunte concreto sobre identificadores: si una columna es un código y no una cantidad —números de pedido, SKU, códigos postales, números de cuenta—, la quieres como texto, no como número. Nunca se hace aritmética con ella, y tipificarla como número es la forma de que 00123 se convierta en 123 para siempre.
Las seis comprobaciones que todo fichero necesita
Una vez que el fichero es un dataset, se puede consultar con SQL corriente. Catalyst registra cada fichero subido como una vista de DuckDB, así que el motor de validación ejecuta el mismo SQL compilado que ejecutaría contra Postgres: sin una ruta de código aparte y sin un segundo lenguaje de reglas.
1. Completitud — nulos en columnas obligatorias
SELECT count(*) AS violations
FROM files.orders_csv
WHERE order_id IS NULL OR trim(order_id) = '';
En ficheros, comprueba siempre la cadena vacía además de NULL. Un CSV no tiene el concepto de nulo: un campo vacío son dos comas seguidas, y los lectores difieren en si eso se convierte en NULL o en ''. La mitad de tus filas puede estar en blanco mientras una comprobación ingenua con IS NULL informa de una completitud perfecta.
2. Unicidad — claves duplicadas
SELECT count(*) AS violations
FROM (
SELECT order_id
FROM files.orders_csv
WHERE order_id IS NOT NULL
GROUP BY order_id
HAVING count(*) > 1
);
Los ficheros no tienen clave primaria ni índice único, así que nunca ha habido nada que impidiera un duplicado. La causa más común es de lo más prosaica: dos exportaciones con rangos de fechas solapados, concatenadas.
3. Conformidad — valores fuera de un conjunto permitido
SELECT count(*) AS violations
FROM files.orders_csv
WHERE status IS NOT NULL
AND status NOT IN ('pending', 'paid', 'shipped', 'refunded');
Mantén la protección frente a nulos: NULL NOT IN (...) es NULL, así que esas filas desaparecen del recuento sin avisar.
Esta comprobación se gana el sueldo en ficheros más que en ningún otro sitio, porque las columnas de hoja de cálculo escritas a mano derivan: paid, Paid, PAID, paid con un espacio al final. Plantéate recortar espacios y pasar a minúsculas dentro de la regla si la fuente la mantiene una persona.
4. Exactitud — números fuera de un rango plausible
SELECT count(*) AS violations
FROM files.orders_csv
WHERE total_amount IS NOT NULL
AND (total_amount < 0 OR total_amount > 100000);
Ojo con los símbolos de moneda y los separadores de miles. €1.234,56 no se parsea como número en la mayoría de los lectores; o falla o se queda en 1.234. Si una columna numérica ha vuelto como texto en la vista previa, la razón es esta.
5. Conformidad — identificadores mal formados
SELECT count(*) AS violations
FROM files.orders_csv
WHERE reference IS NOT NULL
AND NOT regexp_matches(reference, '^ORD-[0-9]{6}#39;);
DuckDB usa RE2, así que los patrones anclados, las clases de caracteres y los cuantificadores funcionan tal cual se escriben, sin traducción, a diferencia de SQL Server. Una comprobación de patrón es la forma más rápida de detectar una columna que Excel ha reformateado: si ORD-000123 se convirtió en ORD-123, esto salta.
6. Oportunidad — frescura
SELECT count(*) AS violations
FROM files.orders_csv
WHERE created_at < now() - INTERVAL 24 HOUR;
Las fechas son el tipo de columna más peligroso de un fichero plano. 03/04/2026 es el 3 de abril o el 4 de marzo según la configuración regional de quien lo generó, y ambas interpretaciones se parsean sin error. Si una columna de fecha importa, comprueba su rango de forma explícita: un fichero en el que todas las fechas caen en los doce primeros días del mes es un fichero parseado con el orden día/mes equivocado.
Contrastar un fichero con tu almacén
La comprobación más valiosa sobre un fichero plano a menudo no tiene que ver con el fichero aislado. Una lista de proveedores, una hoja de correcciones manuales o una exportación de finanzas suele estar pensada para cuadrar con algo que ya tienes:
SELECT count(*) AS violations
FROM files.suppliers_xlsx f
LEFT JOIN files.known_suppliers k ON f.supplier_id = k.id
WHERE f.supplier_id IS NOT NULL
AND k.id IS NULL;
Las filas del fichero que no existen aguas arriba son o registros nuevos o erratas, y saber cuál de las dos cosas antes de cargarlas es justamente el sentido de validar a la entrada en vez de después.
De comprobaciones ad hoc a un contrato de datos
La razón para tratar un fichero como un dataset y no como un script de usar y tirar es que los ficheros se repiten. La misma hoja de proveedores llega cada mes, de la misma persona, con los mismos modos de fallo. Dejar las expectativas escritas una vez significa que la segunda subida se comprueba gratis.
Catalyst usa el Open Data Contract Standard, y un contrato de fichero tiene exactamente el mismo aspecto que uno de almacén:
apiVersion: v3.0.0
kind: DataContract
info:
title: orders_csv
version: 1.0.0
owner: finance-ops
schema:
- name: orders_csv
physicalName: orders_csv
physicalType: table
properties:
- name: order_id
logicalType: string
required: true
primaryKey: true
quality:
- rule: nullCount
dimension: completeness
severity: error
mustBe: "0"
- rule: duplicateCount
dimension: uniqueness
severity: error
mustBe: "0"
- name: status
logicalType: string
quality:
- rule: validValues
dimension: conformity
severity: error
mustBe: "['pending', 'paid', 'shipped', 'refunded']"
- name: total_amount
logicalType: number
quality:
- rule: between
dimension: accuracy
severity: error
mustBe: "[0, 100000]"
- name: reference
logicalType: string
quality:
- rule: regex
dimension: conformity
severity: error
mustBe: "'^ORD-[0-9]{6}#39;"
quality:
- rule: rowCount
dimension: consistency
severity: error
mustBe: "> 0"
Merece la pena señalar dos cosas.
La regla regex sobre reference está aquí con severidad error, mientras que las guías de almacén la ponen en warning. Esa inversión es deliberada: en un almacén, el tipo de la columna ya restringe el valor, así que un patrón que no encaja suele ser una rareza de la introducción de datos. En un fichero, un patrón que no encaja es a menudo la prueba de que el *parseo* salió mal, y eso sí merece una parada.
La regla rowCount a nivel de tabla también importa más aquí. Un resultado vacío tras una subida suele significar que el delimitador o la hoja elegida estaban mal, no que el negocio no tuviera pedidos.
Límites prácticos
Unas cuantas cosas que conviene saber antes de apuntar un flujo de trabajo a esto:
- Las subidas están limitadas a 25 MB por fichero, contrastados contra el
Content-Lengthdeclarado y otra vez durante la lectura, ya que un cliente puede informar mal de su tamaño. Los extractos más grandes tienen su sitio en un almacén. - Los formatos soportados son
.csv,.json(array de objetos o NDJSON),.xlsy.xlsx. Las hojas de cálculo se normalizan a CSV una sola vez en la subida, así que todo lo que viene después trabaja con una única ruta de parseo. - Los ficheros son instantáneas. Un dataset apunta a los bytes que subiste; volver a subirlo es un acto explícito. Ese es el comportamiento correcto por defecto: quieres enterarte de cuándo cambiaron los datos subyacentes.
- Los ficheros subidos se cifran en reposo y solo se leen para resolver una comprobación.
Trampas de los ficheros planos que conviene conocer
| Trampa | Qué ocurre | Qué hacer |
|---|---|---|
Cadena vacía frente a NULL | La completitud pasa con filas en blanco | Comprueba IS NULL OR trim(col) = '' |
| Ceros iniciales eliminados | 00123 pasa a 123 y los joins fallan | Tipifica las columnas de identificador como texto |
| Notación científica | Los IDs largos se vuelven 1.23457E+14 | Tipifica como texto; añade una regla regex |
| Fechas ambiguas | 03/04 se parsea en cualquiera de los dos órdenes | Comprueba el rango de la columna de fecha |
| Delimitador incorrecto | Todo acaba en una sola columna | Confirma la vista previa antes de guardar |
| Codificación incorrecta | é se convierte en é | Vuelve a exportar en UTF-8 |
| Tipo inferido de una muestra | Un N/A tardío rompe una columna numérica | Revisa los tipos inferidos en la vista previa |
| Filas de título encima de la cabecera | Las columnas se llaman Column1, Column2 | Fija la fila de cabecera de forma explícita |
| Hoja equivocada | Se valida la pestaña Notes | Elige la hoja en el momento de subir |
Preguntas frecuentes
¿Cómo valido un fichero CSV sin escribir código?
Súbelo, confirma el parseo (delimitador, carácter de entrecomillado, fila de cabecera, tipos inferidos) y declara después las expectativas: obligatorio, único, valores permitidos, rango numérico, patrón. Catalyst convierte el fichero en un dataset respaldado por DuckDB y compila esas reglas a SQL, así que el mismo formato de contrato cubre un CSV y una tabla de almacén.
¿Puedo validar ficheros de Excel o tengo que convertirlos antes a CSV?
Los .xls y .xlsx se suben directamente; eliges la hoja en el momento de subir. El fichero se normaliza a CSV una vez durante la ingesta para que todo lo posterior tenga una única ruta de parseo. Ten en cuenta que cualquier cosa que haya tocado Excel puede haberse reformateado ya —ceros iniciales eliminados, notación científica, fechas autocorregidas—, que es exactamente lo que están ahí para detectar las reglas de patrón y de rango.
¿Por qué la validación de mi CSV pasa cuando los datos están claramente mal?
Casi siempre porque lo que está mal es el parseo, no las reglas. Las causas habituales: un campo vacío se convirtió en '' en lugar de NULL, así que la comprobación de completitud vio un valor; el delimitador equivocado metió todo en una sola columna, así que la columna comprobada está vacía y todas las protecciones frente a nulos la saltaron; o una columna inferida como texto hace que una regla de rango numérico compare cadenas. Revisa la vista previa parseada antes de fiarte de una ejecución que pasa.
¿De qué tamaño puede ser un fichero que quiera validar?
Catalyst limita las subidas a 25 MB por fichero, lo que cubre la mayoría de las exportaciones manuales, hojas de referencia y listas de proveedores. Más allá de eso, el fichero tiene su sitio en un almacén: cárgalo en PostgreSQL o BigQuery y valídalo allí, donde la poda de particiones y los índices abaratan las comprobaciones.
¿Validar un fichero es distinto de validar una tabla de base de datos?
Las reglas son idénticas: el mismo contrato, las mismas seis comprobaciones. Lo que cambia es todo lo anterior. Una base de datos ya ha impuesto tipos y rechazado filas mal formadas; un fichero no ha impuesto nada, así que el parseo y el tipado son donde viven la mayoría de los defectos. Acierta con el parseo y el resto es el mismo trabajo.