OdoKeep ya está en el App Store. Descárgalo gratis.

Todos los artículos

Importación de datos

Un importador CSV en TypeScript que no adivina tu historial de combustible

Detectar convenciones CSV sin cambiar fechas, unidades ni el estado del tanque. Un importador TypeScript que permite revisar antes de guardar.

Actualizado el

En este artículo

03/09/2026 es una fecha válida en dos historias distintas.

Si la importo como 9 de marzo cuando el usuario quería decir 3 de septiembre, el parser no falla. La base de datos acepta el registro. La cronología parece ordenada. Todos los informes que dependen de esa fecha quedan equivocados.

Ese era el problema que quería resolver en el importador CSV de OdoKeep: errores que producen datos válidos.

OdoKeep es un diario de vehículos en React Native, Expo y TypeScript. Quien lo prueba puede tener años de cargas de combustible en otra aplicación o en una hoja de cálculo. Pedirle que abandone ese historial impone un costo importante antes de que decida si le gusta la nueva aplicación.

Leer un CSV reduce parte de ese costo. Conservar el significado de sus números es la parte mayor.

Inspeccionar, construir y después guardar

La implementación está en lib/csv-import.ts y tiene dos resultados distintos. La inspección produce la tabla, las correspondencias sugeridas, la fuente detectada, las convenciones y las preguntas pendientes. La construcción produce objetos VehicleRecordDraft normales y una lista de filas omitidas con sus motivos. La escritura usa por separado la ruta de importación existente del garaje.

Así el usuario puede revisar una fila de su propio archivo antes de añadir registros.

Detectar el separador interpretando el archivo

Una hoja portuguesa puede separar columnas con punto y coma y usar comas en casi todos los números:

Data;Odometro;Litros;Preco/l;Valor total
03/09/2026;142350;31,93;1,899;60,64
04/09/2026;142890;28,5;1,879;53,55

Contar comas y puntos y coma no identifica de forma fiable el separador. Pueden pertenecer a números o a notas entre comillas.

El importador prueba coma, punto y coma, tabulador y barra vertical interpretando el texto con cada candidato. Puntúa la consistencia entre la anchura de las filas y la cabecera, con una pequeña contribución del número de columnas. Un candidato que nunca produce al menos dos columnas no aporta evidencia útil.

El parser recorre caracteres y mantiene el estado de las comillas. Admite separadores y saltos de línea dentro de campos entrecomillados, comillas duplicadas, marca de orden de bytes y los finales de línea esperados. Separar primero por líneas destruiría una nota multilínea.

Esto responde a archivos que las personas guardan y editan; no significa reconocer cualquier CSV mal formado. Las etapas siguientes todavía deben establecer si la tabla puede utilizarse.

Resolver números y fechas con evidencia del archivo

1.234 puede ser un decimal o un entero agrupado. Un único valor puede no resolver la convención, pero otras columnas numéricas sí pueden aportar información.

El detector examina cantidad, precio unitario, costo total y odómetro. La presencia de ambos separadores ofrece evidencia fuerte. Si solo aparece uno, una cantidad de dígitos posterior distinta de tres puede distinguir el decimal de la agrupación de miles.

Los valores ambiguos no aportan evidencia. Si los datos asignados no resuelven la convención y ningún perfil conocido proporciona un valor predeterminado, la inspección devuelve null y la pantalla pregunta.

Las fechas siguen un criterio parecido. Un año inicial identifica ese formato. Con el año al final, un componente mayor de doce puede establecer cuál es el día. Si todas las fechas caen en los primeros doce días, la columna puede no contener la respuesta.

Es un resultado válido. La configuración regional del usuario no demuestra qué convenciones siguió un archivo creado en otro sitio.

Anteponer la evidencia al perfil

Existen perfiles Fuelly y Drivvo, subordinados a la evidencia del archivo:

const named = unitFromHeaders(table.headers, mapping);

return {
  table,
  mapping,
  source,
  decimal: detectDecimalStyle(numeric) ?? source?.decimal ?? null,
  dateOrder: detectDateOrder(column("date")) ?? source?.dateOrder ?? null,
  odometerUnit: named.odometerUnit ?? source?.odometerUnit ?? null,
  // Other inspection fields are omitted here.
};

Las unidades de cantidad siguen la misma prioridad: una unidad explícita prevalece sobre el valor del perfil.

Un archivo con estructura Fuelly puede proceder de una cuenta métrica o haberse guardado de nuevo en una hoja europea. Reconocer el origen ayuda a proponer correspondencias, pero no autoriza a contradecir el contenido.

Algunas correspondencias requieren conocimiento del dominio. Fuelly distingue odometer de miles, y fuelup date de date added. Asignar la distancia desde el carga de combustible anterior al odómetro produce valores plausibles con significado incorrecto. Usar la fecha de introducción como fecha de compra puede mover años de historial a la tarde en que alguien los escribió.

Su campo price es un precio unitario, por lo que puede calcularse un total ausente multiplicándolo por la cantidad. En la estructura Drivvo se consideran primero las columnas de precio unitario y después las de costo total, evitando que cabeceras parecidas reclamen el mismo campo.

Conviene precisar el alcance de los perfiles. Se construyeron con documentación e importadores existentes, no con exportaciones manuales de cuentas reales de Fuelly y Drivvo realizadas durante este trabajo. Algunas cabeceras y valores siguen siendo supuestos, especialmente la escritura exacta de las cabeceras Drivvo. Las pruebas verifican esas estructuras, no todas las versiones e idiomas que podrían exportar esos proveedores.

Por eso la asignación permanece visible y editable. Un error del perfil debe poder corregirse antes de convertirse en historial.

Desconocido también es un estado del tanque

El cálculo de consumo necesita distinguir tanque lleno, llenado parcial y estado desconocido.

Algunas fuentes expresan el indicador como carga de combustible parcial. El perfil invierte los valores no vacíos, pero una celda vacía sigue siendo desconocida:

isFullTank: rawFull
  ? options.fullTankInverted
    ? !isAffirmative(rawFull)
    : isAffirmative(rawFull)
  : undefined

Un indicador parcial vacío no demuestra que el tanque esté lleno. La ausencia de la columna tampoco demuestra que todos los llenados sean parciales. Ambos atajos alterarían el consumo sin producir un error visible del parser.

Validar el día y la fila

Una cadena ISO con solo fecha se interpreta en JavaScript como medianoche UTC. En un huso al oeste de UTC, puede mostrarse como la tarde anterior.

El importador valida primero el día del calendario, incluida la duración real del mes. El 30 de febrero no se desplaza a marzo. Después escribe medianoche local con el desplazamiento horario del dispositivo, igual que los formularios manuales. El 3 de septiembre elegido debe seguir siendo 3 de septiembre en la cronología y los totales mensuales.

El constructor rechaza valores negativos y superiores a los límites compartidos de la aplicación antes de escribir. Un odómetro mal tecleado o una fila parecida a un reembolso pasa a ser una fila omitida identificable, en vez de hacer fallar un bloque de registros correctos.

También se omite una fila con fecha pero sin odómetro, cantidad ni costo utilizable. Añadir una entrada que no describe nada solo alargaría el historial.

El resultado separa borradores y filas omitidas. El usuario puede corregir el archivo o continuar con lo leído. Tras la confirmación, los borradores usan el escritor de importación y la cola sin conexión existentes. El lector CSV no añade otro modelo de persistencia.

Investigar sin escribir

El script csv:probe reutiliza inspección y construcción sin guardar. Imprime separador, convenciones, asignaciones, ejemplos y filas rechazadas. Si usa valores provisionales para un diagnóstico, los identifica como supuestos. Permite inspeccionar un ejemplo sin abrir la aplicación ni una cuenta.

Las pruebas cubren punto y coma con coma decimal, ambos perfiles, convenciones ambiguas, notas multilínea entre comillas, fechas inválidas, negativos, unidades explícitas que prevalecen sobre perfiles y estados de tanque desconocidos. El caso con formatos mezclados resulta útil porque estos problemas suelen llegar juntos.

No se garantiza importar cualquier exportación sin intervención. La implementación propone, valida lo posible y deja las decisiones restantes en la interfaz. Para trasladar un historial de años, una pregunta visible sobre unidades cuesta poco frente a reinterpretar silenciosamente todas las entradas.

Si ya guardas combustible en un CSV, este es el flujo que construyo en OdoKeep: inspeccionar el archivo, comprobar columnas y unidades e incorporar los registros útiles al mismo historial que las nuevas entradas. Puedes probarlo en la aplicación iOS.