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:

  1. 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.
  2. Fila de cabecera. ¿La primera columna se llama order_id o «Exportación de ventas — T3»?
  3. 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.
  4. 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:

Trampas de los ficheros planos que conviene conocer

TrampaQué ocurreQué hacer
Cadena vacía frente a NULLLa completitud pasa con filas en blancoComprueba IS NULL OR trim(col) = ''
Ceros iniciales eliminados00123 pasa a 123 y los joins fallanTipifica las columnas de identificador como texto
Notación científicaLos IDs largos se vuelven 1.23457E+14Tipifica como texto; añade una regla regex
Fechas ambiguas03/04 se parsea en cualquiera de los dos órdenesComprueba el rango de la columna de fecha
Delimitador incorrectoTodo acaba en una sola columnaConfirma la vista previa antes de guardar
Codificación incorrectaé se convierte en éVuelve a exportar en UTF-8
Tipo inferido de una muestraUn N/A tardío rompe una columna numéricaRevisa los tipos inferidos en la vista previa
Filas de título encima de la cabeceraLas columnas se llaman Column1, Column2Fija la fila de cabecera de forma explícita
Hoja equivocadaSe valida la pestaña NotesElige 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.