Archivos y entrada y salida
La entrada y salida conecta el programa con recursos que fallan, llegan por partes y tienen formatos. Separar bytes, parsing y lógica vuelve esos límites controlables.
Para este tema conviene tener claro:Cadenas de texto
Dentro del programa, una suma suele terminar o lanzar un error predecible. Afuera hay archivos que faltan, permisos, discos llenos y datos cortados. Entrada y salida es una frontera con un mundo que no controlamos.
Bytes, texto, datos y dominio
Un archivo contiene bytes. La codificación transforma bytes en texto; el parser transforma texto en datos; la validación decide si esos datos pertenecen al dominio. Mezclar las tres capas produce errores difíciles de ubicar.
Una ruta válida no garantiza que el contenido exista ni tenga el formato esperado. Cada etapa necesita un resultado o error que preserve contexto suficiente para diagnosticarla.
Antes de seguir, predecí
Abrir es pedir un recurso prestado
Abrir un archivo adquiere un recurso del sistema operativo. Debe cerrarse incluso si el procesamiento falla. Los lenguajes ofrecen bloques o APIs que garantizan limpieza al salir de un alcance.
Leer todo de una vez es simple, pero usa memoria proporcional al archivo. Para entradas grandes, un stream procesa fragmentos y mantiene acotada la memoria, a cambio de manejar límites parciales.
Escribir sin dejar el archivo a medias
Una escritura puede interrumpirse y dejar contenido incompleto. Para reemplazar configuración o datos importantes, una estrategia común escribe un archivo temporal, fuerza las comprobaciones y lo renombra dentro del mismo sistema de archivos.
Eso reduce la ventana de corrupción, pero durabilidad y atomicidad exactas dependen del sistema. No hay que presentar “guardar” como una única operación infalible.
CSV y JSON: las reglas que muerden
CSV, JSON y formatos binarios tienen reglas propias. Separadores escapados, saltos de línea, zonas horarias y versiones de schema convierten un ejemplo pequeño en un contrato real.
Validar después de parsear evita que datos estructuralmente válidos pero semánticamente imposibles entren al núcleo. Los mensajes de error deberían indicar archivo y ubicación sin exponer contenido sensible innecesario.
Las tres capas, una por una
Un archivo de precios que llega todos los días. La ruta existe, y eso es lo único que sabemos.
Leer entero contra leer de a pedazos
import { readFile } from 'node:fs/promises';
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
// 1. Todo en memoria: simple, y sólo sirve si el archivo es chico
const parseWhole = async (path: string): Promise<string[]> => {
const content = await readFile(path, 'utf8'); // un archivo de 2 GB son 2 GB de memoria
return content.split('\n');
};
// 2. De a una línea: memoria constante, sirva el archivo que sirva
const countLines = async (path: string): Promise<number> => {
const stream = createReadStream(path, { encoding: 'utf8' });
const lines = createInterface({ input: stream, crlfDelay: Infinity });
let total = 0;
for await (const line of lines) {
if (line.trim() !== '') total += 1;
}
return total;
};La diferencia entre las dos no es de estilo: la primera funciona en desarrollo con el archivo de
prueba de diez líneas y tira el proceso en producción con el archivo real. Y el encoding no es
opcional: sin él, lo que llega son bytes, y partir bytes por el medio de un carácter de varios
bytes produce texto roto.
La frontera con el mundo
| Lo que puede fallar | Cuándo se nota | Qué corresponde hacer |
|---|---|---|
| El archivo no existe | al abrir | error claro con la ruta adentro |
| No hay permisos | al abrir | lo mismo: es un problema de entorno, no de datos |
| La codificación no es la esperada | nunca: salen caracteres raros | declararla y validar |
| El contenido no tiene el formato | al parsear | decir qué línea y qué se esperaba |
| El disco se llena al escribir | a mitad de camino | escribir a un temporal y renombrar al final |
| El proceso muere escribiendo | después: queda medio archivo | lo mismo: renombrar es atómico |
Cierre
Entrada y salida exige pensar en recursos, fragmentos y fallas parciales. Separá lectura de bytes, decodificación, parsing y validación. Así la lógica principal trabaja con datos confiables y la frontera conserva los detalles del error.
Autoevaluación
¿Lo entendiste?
Práctica