Leer y escribir archivos de texto en TypeScript se hace con las mismas funciones que en JavaScript, las de node:fs/promises. Lo que añade TypeScript es lo que pasa después: convertir cada línea en un objeto con tipo, limitar los valores posibles y saber qué error recibes.
También te puede interesar
De un CSV a objetos con tipo
El archivo ventas.csv:
fecha,producto,cantidad,precio 2026-10-18,Audífonos,2,1299.99 2026-10-18,Webcam,1,899 2026-10-19,Bocina,3,649.50 2026-10-19,Cable HDMI,10,189 2026-10-20,Audífonos,1,1299.99
Lo leemos línea a línea con readLines, convertimos cada línea en una Venta y sumamos por producto:
import { open } from "node:fs/promises";
interface Venta {
fecha: string;
producto: string;
cantidad: number;
precio: number;
}
// Convierte una línea del CSV en una Venta, o devuelve null si está mal.
function aVenta(linea: string): Venta | null {
const [fecha, producto, cantidad, precio] = linea.split(",");
if (!fecha || !producto || !cantidad || !precio) return null;
const venta = { fecha, producto, cantidad: Number(cantidad), precio: Number(precio) };
return Number.isNaN(venta.cantidad) || Number.isNaN(venta.precio) ? null : venta;
}
const archivo = await open("ventas.csv");
const ventas: Venta[] = [];
let primera = true;
for await (const linea of archivo.readLines()) {
if (primera) { primera = false; continue; } // la cabecera
const v = aVenta(linea);
if (v) ventas.push(v);
}
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
const porProducto = new Map<string, number>();
for (const v of ventas) {
porProducto.set(v.producto, (porProducto.get(v.producto) ?? 0) + v.cantidad * v.precio);
}
for (const [producto, total] of porProducto) console.log(producto.padEnd(12), usd.format(total));npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es csv.ts
node csv.ts
salidaAudífonos $3,899.97 Webcam $899.00 Bocina $1,948.50 Cable HDMI $1,890.00
aVenta devuelve Venta | null: una línea mal formada no rompe el programa, se descarta. Y como devuelve null, TypeScript obliga a comprobarlo (if (v)) antes de guardar la venta.
Por qué hay que comprobar cada campo
split devuelve un string[]. Si la línea tiene menos comas de las esperadas, los últimos campos son undefined, pero con strict a secas TypeScript no lo avisa:
const linea = "2026-10-18,Audífonos,2,1299.99";
const [fecha, producto, cantidad, precio] = linea.split(",");
console.log(producto.toUpperCase());npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es sin-comprobar.ts
tsc(sin errores)
Con la opción noUncheckedIndexedAccess, cada elemento puede ser undefined y hay que comprobarlo, como hace aVenta:
npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es --noUncheckedIndexedAccess sin-comprobar.ts
tscsin-comprobar.ts(3,13): error TS18048: "producto" es posiblemente "undefined".
Escribir: un registro con niveles fijos
appendFile añade al final del archivo. El tipo Nivel limita los niveles a tres:
import { appendFile, readFile, writeFile } from "node:fs/promises";
type Nivel = "INFO" | "AVISO" | "ERROR";
async function registrar(nivel: Nivel, mensaje: string): Promise<void> {
const hora = new Date("2026-10-20T16:05:00Z").toISOString();
await appendFile("/tmp/registro.log", `${hora} [${nivel}] ${mensaje}\n`, "utf8");
}
await writeFile("/tmp/registro.log", "", "utf8");
await registrar("INFO", "Servidor arrancado");
await registrar("AVISO", "Disco al 85 %");
console.log(await readFile("/tmp/registro.log", "utf8"));node escribir.ts
salida2026-10-20T16:05:00.000Z [INFO] Servidor arrancado 2026-10-20T16:05:00.000Z [AVISO] Disco al 85 %
Un nivel inventado no compila:
type Nivel = "INFO" | "AVISO" | "ERROR";
declare function registrar(nivel: Nivel, mensaje: string): Promise<void>;
await registrar("WARN", "Disco al 85 %");tscnivel-error.ts(3,17): error TS2345: No se puede asignar un argumento de tipo ""WARN"" al parámetro de tipo "Nivel".
El archivo que no existe
Si el archivo no existe, Node lanza un error con code: "ENOENT". En el catch, el error es unknown, y la propiedad code no está en el tipo Error estándar. @types/node trae el tipo NodeJS.ErrnoException, y una guarda de tipo lo comprueba:
import { readFile } from "node:fs/promises";
// ¿El error es de Node y trae un código? Una guarda para no adivinar.
function esErrorDeNode(e: unknown): e is NodeJS.ErrnoException {
return e instanceof Error && "code" in e;
}
async function leerOpcional(ruta: string): Promise<string | null> {
try {
return await readFile(ruta, "utf8");
} catch (e) {
if (esErrorDeNode(e) && e.code === "ENOENT") return null; // no existe: no pasa nada
throw e; // otro problema: que se vea
}
}
const texto = await leerOpcional("no-existe.txt");
console.log(texto ?? "(sin archivo, se usan los valores por omisión)");npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es noexiste.ts
node noexiste.ts
salida(sin archivo, se usan los valores por omisión)
Venta diga que cantidad es number no convierte nada: el texto del archivo hay que pasarlo por Number() y descartar el NaN, como hace aVenta. El tipo te obliga a hacerlo, porque un string no cabe en un number.Todo el código de esta página se comprobó con TypeScript 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. Documentación oficial: API de fs/promises de Node.js.