Apariencia
📘 Clase 11 — Módulos en TypeScript sin monolitos
TypeScript · 2026-08-05 · Carpeta:
02-Ejercicios/Modulos⬅️ Volver al índice de clases
🎯 Qué aprendí
- Qué es un monolito en código y por qué evitarlo.
- Cómo exportar funciones y constantes con
export function/export const. - Cómo importar piezas específicas de otro archivo con
import { ... }. - Por qué
tsc main.tscompila también los archivos quemain.tsimporta (compilación en cascada, ya vista en la Clase 8). - Un detalle importante de mi propio proyecto: la ruta del import necesita
.jsal final para poder ejecutarse, aunque TypeScript compile igual sin ella. - Cuándo conviene refactorizar un archivo grande a varios módulos pequeños.
📚 Definiciones clave
| Término | Qué es | Ejemplo de esta clase |
|---|---|---|
export function | Marca una función como visible fuera de su archivo. Sin export, la función solo existe dentro de ese archivo. | export function suma(...) { ... } |
export const | Igual que export function, pero para una constante (valores fijos, configuración, etc.). | export const Pi = 3.1416; |
import { } | Trae elementos específicos de otro archivo — las llaves { } indican "esto puntual", no todo el archivo. | import { suma, Pi } from "./calculator.js"; |
tsc main.ts | Compila main.ts y, en cascada, también cualquier archivo que main.ts importe (sin tener que compilarlos uno por uno a mano). | tsc main.ts → genera main.js y calculator.js |
📖 PARTE TEÓRICA
🧱 1. ¿Qué es un monolito y por qué evitarlo?
Un monolito es ese archivo gigante que concentra toda la lógica de tu aplicación. Suena cómodo al inicio, pero se vuelve un problema cuando aparecen errores: entre más líneas tiene un archivo, más probable es que aparezca un bug, y más difícil será ubicarlo. Varios archivos pequeños, cada uno con una responsabilidad clara, hacen tu vida más sencilla: navegas mejor, depuras más rápido, y tu equipo entiende el proyecto sin pelearse con un muro de texto.
🧪 Entrevista: ¿Qué es un monolito en programación? Un archivo o aplicación que concentra toda la lógica en un solo bloque de código — difícil de mantener, escalar y depurar. Un módulo es justo lo contrario: fragmentar funcionalidad en piezas pequeñas y reutilizables.
📤 2. export: exponer funciones y constantes
export marca una función, clase o constante como disponible hacia afuera del archivo. Sin export, la función solo vive dentro de su propio archivo y nadie más puede usarla — es la llave que abre la puerta del módulo.
typescript
export function suma(num1: number, num2: number): number {
return num1 + num2;
}
export const Pi = 3.1416;Las cuatro operaciones de una calculadora se ven prácticamente iguales; lo único que cambia es el operador aritmético — separación clara de responsabilidades, nada de ciencia oculta.
📥 3. import { }: traer piezas específicas de otro archivo
La sintaxis usa llaves para traer elementos puntuales, no el archivo completo (a diferencia de importar una clase entera, como en clases anteriores):
typescript
import { suma, resta, multiplicacion, division } from "./calculator.js";
import { Pi } from "./calculator.js";🧪 Entrevista: ¿Cómo importo solo una función de un archivo en TypeScript?
import { nombreFuncion } from './ruta'. Las llaves indican que traes elementos específicos, no todo el archivo.
💡 Si varias cosas vienen del mismo archivo, se pueden combinar en una sola línea:
import { suma, resta, Pi } from "./calculator.js";— funciona igual, es solo más compacto. Verificado en terminal.
⚠️ 4. La ruta del import: con .js o sin extensión (gotcha real de tu proyecto)
Aquí hay un detalle importante que vale la pena aclarar, porque puede confundir: se puede escribir el import sin la extensión del archivo, y TypeScript compila sin quejarse. Pero eso no significa que siempre vaya a funcionar al ejecutarlo con node.
typescript
import { suma } from "./calculator"; // compila bien...
import { suma } from "./calculator.js"; // ...pero SOLO esta versión corre con nodeLo comprobé en terminal: la primera compila perfecto (tsc no da ningún error), pero al ejecutar con node truena:
text
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../calculator' imported from '.../main.js'
Did you mean to import "./calculator.js"?¿Por qué pasa esto en tu proyecto? Tu package.json raíz tiene "type": "module", lo que activa las reglas estrictas de ESM de Node — y ESM exige la extensión completa en los imports relativos, sin adivinar. TypeScript solo compila tipos; no valida cómo va a resolver los módulos node en tiempo de ejecución. Por eso tu main.ts real ya trae el .js — no es un capricho, es lo correcto para tu proyecto.
📝 Si en algún video o tutorial ves el import sin extensión y "funciona", probablemente ese proyecto NO tiene
"type": "module"en supackage.json(usa CommonJS por defecto, que sí resuelve la ruta sin extensión). En el tuyo, siempre con.js.
🔁 5. Compilar y ejecutar: tsc main.ts + compilación en cascada
bash
cd 02-Ejercicios/Modulos
tsc main.ts
node main.jsAunque solo le pediste a tsc que compile main.ts, TypeScript detecta que main.ts importa de calculator.ts y genera también calculator.js — el mismo efecto dominó que ya viste con la herencia en la Clase 8.
💡 Esta carpeta no tiene
tsconfig.json(a propósito, estilo del profesor) — por esotsc main.tscon nombre no da el errorTS5112que sí viste en la Clase 9: sintsconfig.json, no hay nada que "ignorar".
🧭 6. ¿Cuándo conviene refactorizar a módulos?
No hace falta escribir modular desde el minuto uno. Una estrategia práctica:
- Construye primero un archivo grande que funcione, aunque sea extenso.
- Identifica bloques de funcionalidad que se repiten o tienen una responsabilidad clara.
- Mueve esos bloques a archivos pequeños y expórtalos.
- Importa solo lo necesario en tu archivo principal.
💡 La ganancia real: cambias una función en su archivo pequeño, y el cambio se hereda al archivo principal sin tocar nada más. Además, archivos chicos y enfocados reducen conflictos de Git y bajan la curva para quien se une al proyecto.
💻 PARTE PRÁCTICA
Archivos trabajados: 02-Ejercicios/Modulos/calculator.ts y 02-Ejercicios/Modulos/main.ts
typescript
// calculator.ts
export function suma(numero1: number, numero2: number): number {
return numero1 + numero2;
}
export function resta(numero1: number, numero2: number): number {
return numero1 - numero2;
}
export function multiplicacion(numero1: number, numero2: number): number {
return numero1 * numero2;
}
export function division(numero1: number, numero2: number): number {
return numero1 / numero2;
}
export const Pi = 3.1416;typescript
// main.ts
import { suma, resta, multiplicacion, division } from "./calculator.js";
import { Pi } from "./calculator.js";
console.log(suma(10, 154));
console.log(resta(120, 15));
console.log(resta(Pi, 1));bash
cd 02-Ejercicios/Modulos
tsc main.ts && node main.jsSalida real verificada:
$ tsc main.ts
$ node main.js
164
105
2.1416Todo verificado y corriendo. ✅
🏋️ EJERCICIOS CON SOLUCIÓN
Ejercicio 1 — export function + import básico
Crea saludos.ts con export function saludar(nombre: string): string. Impórtala en main.ts y salúdate a ti mismo.
💡 ¿Sabías que…? — repaso de export / import
export marca lo que un archivo ofrece hacia afuera; import { nombre } from "./archivo.js" lo trae a otro archivo. Ojo con la ruta: se escribe con .js (el archivo compilado que Node ejecutará), aunque tú programes en .ts. Ejemplo de referencia:
typescript
// voz.ts
export function gritar(texto: string): string {
return texto.toUpperCase() + "!!!";
}typescript
// main.ts
import { gritar } from "./voz.js";
console.log(gritar("hola")); // HOLA!!!Ver solución
typescript
// saludos.ts
export function saludar(nombre: string): string {
return `Hola, ${nombre}`;
}typescript
// main.ts
import { saludar } from "./saludos.js";
console.log(saludar("Styp"));Terminal esperada:
$ tsc main.ts
$ node main.js
Hola, StypEjercicio 2 — export const
Crea constantes.ts con export const IVA = 0.18. Impórtala y calcula el IVA de 100.
💡 ¿Sabías que…? — no solo se exportan funciones
Cualquier cosa con nombre se puede exportar: funciones, constantes, clases e interfaces — la sintaxis es la misma. Exportar constantes es el patrón típico para valores compartidos (configuración, límites, tasas): se definen una sola vez y todos los archivos importan el mismo valor. Ejemplo de referencia:
typescript
// config.ts
export const LIMITE = 100;typescript
// main.ts
import { LIMITE } from "./config.js";
console.log(LIMITE / 2); // 50Ver solución
typescript
// constantes.ts
export const IVA = 0.18;typescript
// main.ts
import { IVA } from "./constantes.js";
console.log(100 * IVA);Terminal esperada:
$ tsc main.ts
$ node main.js
18Ejercicio 3 — Combinar varios imports en una sola línea
En la sección de Práctica, el main.ts quedó con dos líneas de import, y ambas traen cosas del mismo archivo: una trae las 4 funciones de calculator.js y la otra trae la constante Pi.
Tu tarea: reescribe esos imports en una sola línea, sin tocar los console.log. El programa debe imprimir exactamente lo mismo que en la Práctica (164, 105, 2.1416) — si la salida cambia, algo rompiste.
💡 ¿Sabías que…? — las llaves del import son una lista
Las llaves { } no son sintaxis fija de un solo nombre: son una lista donde puedes poner tantos nombres como quieras, separados por comas — siempre que todos vengan del mismo archivo. Ejemplo de referencia:
typescript
import { ancho, alto, area } from "./medidas.js";Ver solución
Las llaves { } del import son una lista: puedes poner todos los nombres que quieras separados por comas, siempre que vengan del mismo archivo. Pi no es especial por ser constante — funciones, constantes e interfaces viajan igual en la misma lista.
typescript
// main.ts — mismo comportamiento, 1 solo import
import { suma, resta, multiplicacion, division, Pi } from "./calculator.js";
console.log(suma(10, 154));
console.log(resta(120, 15));
console.log(resta(Pi, 1));Terminal esperada (idéntica a la de la Práctica — eso confirma que solo cambió la forma de importar, no el comportamiento):
$ tsc main.ts
$ node main.js
164
105
2.1416💡 Si todo viene del mismo archivo, no hace falta un
importpor cada cosa: una sola línea con la lista completa. Separar en variosimportdel mismo archivo no es un error (compila igual), solo es ruido innecesario.
Ejercicio 4 — Renombrar un import con as
Importa suma pero renómbrala a add con as, y úsala con ese nuevo nombre.
💡 ¿Sabías que…? — as renombra solo en tu archivo
as cambia el nombre solo en el archivo que importa — el archivo original no se toca, y otros archivos pueden seguir importándolo con su nombre real. El caso típico: dos archivos exportan algo con el mismo nombre y necesitas ambos sin choque. Ejemplo de referencia:
typescript
import { suma as sumaCalculadora } from "./calculator.js";
import { suma as sumaMatrices } from "./matrices.js";Ver solución
typescript
import { suma as add } from "./calculator.js";
console.log(add(2, 3));Terminal esperada:
$ tsc main.ts
$ node main.js
5💡 Útil cuando dos archivos distintos exportan algo con el mismo nombre, y necesitas importar ambos sin que choquen.
Ejercicio 5 — Exportar una interface y usarla como tipo
Crea modelos.ts con export interface Persona { nombre: string; edad: number }. Impórtala en main.ts y úsala para tipar un objeto.
💡 ¿Sabías que…? — repaso rápido de interface
Una interface es un molde que describe la forma de un objeto: qué propiedades tiene y de qué tipo es cada una. No genera código JavaScript — solo existe para que TypeScript valide que el objeto cumple el molde (visto a fondo en Clase 5). Ejemplo de referencia:
typescript
interface Producto {
titulo: string;
precio: number;
}
const libro: Producto = { titulo: "El Quijote", precio: 49.9 };Si a libro le falta una propiedad o le pones un tipo equivocado, tsc lo marca como error. En este ejercicio harás lo mismo, pero con la interface viviendo en otro archivo (por eso necesita export).
Ver solución
typescript
// modelos.ts
export interface Persona {
nombre: string;
edad: number;
}typescript
// main.ts
import { Persona } from "./modelos.js";
const persona: Persona = { nombre: "Ana", edad: 25 };
console.log(`${persona.nombre} tiene ${persona.edad} años`);Terminal esperada:
$ tsc main.ts
$ node main.js
Ana tiene 25 años💡 Las
interfacetambién se exportan/importan igual que funciones y constantes.
⚠️ Ojo con mayúscula vs minúscula:
Persona(mayúscula) es la interface — el molde, solo existe para TypeScript.persona(minúscula) es la variable — el objeto real con los datos. En elconsole.logvapersona.nombre(minúscula), porque los datos viven en la variable. Si escribesPersona.nombre,tsclanza:error TS2693: 'Persona' only refers to a type, but is being used as a value here.
Ejercicio 6 — Exportar una clase
Crea animal.ts con export class Animal (constructor con nombre, método hacerSonido()). Impórtala y úsala en main.ts.
💡 ¿Sabías que…? — repaso rápido de clases
Una clase es un molde con comportamiento: propiedades + métodos, y se instancia con new (visto a fondo en Clase 6). El atajo constructor(public marca: string) declara la propiedad y la asigna en un solo paso. Y se exporta igual que una función: export class. Ejemplo de referencia:
typescript
// vehiculo.ts
export class Coche {
constructor(public marca: string) {}
describir(): string {
return `Un coche marca ${this.marca}`;
}
}typescript
// main.ts
import { Coche } from "./vehiculo.js";
const miCoche = new Coche("Toyota");
console.log(miCoche.describir()); // Un coche marca ToyotaVer solución
typescript
// animal.ts
export class Animal {
constructor(public nombre: string) {}
hacerSonido(): string {
return `${this.nombre} hace un sonido`;
}
}typescript
// main.ts
import { Animal } from "./animal.js";
const perro = new Animal("Perro");
console.log(perro.hacerSonido());Terminal esperada:
$ tsc main.ts
$ node main.js
Perro hace un sonidoEjercicio 7 — Forzar el error de importar algo NO exportado
Antes de correrlo: si calculator.ts tiene una función sin export, ¿qué error esperas al intentar importarla? Anticipa y verifica.
💡 ¿Sabías que…? — sin export es "privado del archivo"
Todo lo que NO lleva export queda encerrado en su archivo: existe, funciona ahí dentro, pero nadie de afuera puede importarlo. Y el aviso llega en compilación: tsc revisa los imports antes de generar el .js, así que el error aparece sin ejecutar nada — Node nunca llega a correr. Esa es una de las grandes ventajas de TypeScript: el error se ve en tu terminal, no en producción.
Ver solución
typescript
// calculator.ts (agregado, SIN export)
function restaPrivada(a: number, b: number): number {
return a - b;
}typescript
// main.ts
import { restaPrivada } from "./calculator.js"; // ❌ forzado a propósito
console.log(restaPrivada(5, 2));Terminal esperada:
$ tsc main.ts
error TS2459: Module '"./calculator.js"' declares 'restaPrivada' locally, but it is not exported.💡 Sin
export, la función existe pero queda "encerrada" en su archivo — nadie de afuera puede verla, ni siquiera con el nombre exacto.
Ejercicio 8 — Cadena de 3 archivos (import en cascada)
Crea c.ts (exporta version), b.ts (importa de c.ts y exporta info()), y main.ts (importa info de b.ts, sin tocar c.ts directamente).
💡 ¿Sabías que…? — un archivo puede importar Y exportar a la vez
Un módulo puede ser consumidor y proveedor al mismo tiempo: importa lo que necesita de un archivo y exporta algo propio para el siguiente. Así se forman capas, donde cada archivo solo conoce a su vecino inmediato:
main.ts → b.ts → c.ts
(interfaz) (lógica) (datos)Es la base de cómo se organizan los proyectos reales: main no necesita saber que c.ts existe — si mañana c.ts cambia, solo b.ts se entera.
Ver solución
typescript
// c.ts
export const version = "1.0.0";typescript
// b.ts
import { version } from "./c.js";
export function info(): string {
return `Versión: ${version}`;
}typescript
// main.ts
import { info } from "./b.js";
console.log(info());Terminal esperada:
$ tsc main.ts
$ node main.js
Versión: 1.0.0💡
tsc main.tscompiló los TRES archivos de un tirón — la cascada sigue la cadena completa (main→b→c), no solo un nivel.
Ejercicio 9 — export default vs export nombrado
Crea config.ts con un export default (un objeto de configuración). Impórtalo en main.ts sin llaves (así se importa un export default).
Ver solución
typescript
// config.ts
const configuracion = { entorno: "produccion" };
export default configuracion;typescript
// main.ts
import configuracion from "./config.js"; // SIN llaves: es un export default
console.log(configuracion.entorno);Terminal esperada:
$ tsc main.ts
$ node main.js
produccion💡 Un archivo solo puede tener un
export default, pero cuantosexportnombrados (export const,export function) quieras. Al importar el default, no llevas llaves; al importar nombrados, sí.
Ejercicio 10 — Integrador: mini calculadora modular en 3 archivos
Organiza una "mini calculadora" sin monolito: operaciones.ts (funciones sumar y multiplicar), constantes.ts (DESCUENTO = 0.1), y main.ts que combine ambos para calcular un total con descuento.
Ver solución
typescript
// operaciones.ts
export function sumar(a: number, b: number): number {
return a + b;
}
export function multiplicar(a: number, b: number): number {
return a * b;
}typescript
// constantes.ts
export const DESCUENTO = 0.1;typescript
// main.ts
import { sumar, multiplicar } from "./operaciones.js";
import { DESCUENTO } from "./constantes.js";
const subtotal = sumar(80, 20);
const total = subtotal - multiplicar(subtotal, DESCUENTO);
console.log(`Subtotal: ${subtotal}, Total con descuento: ${total}`);Terminal esperada:
$ tsc main.ts
$ node main.js
Subtotal: 100, Total con descuento: 90💡 Este es justo el patrón de la sección 6 de la teoría: piezas pequeñas, cada una con una responsabilidad, combinadas en
main.ts.
❓ Preguntas y respuestas (autoevaluación)
1. ¿Qué hace la palabra export?
Marca una función, clase o constante como disponible fuera de su archivo. Sin
export, solo se puede usar dentro del mismo archivo.
2. ¿Cómo importo solo una función específica de un archivo?
import { nombreFuncion } from './ruta'. Las llaves indican que traes elementos puntuales, no el archivo completo.
3. ¿Por qué tsc main.ts también genera calculator.js si solo compilé main.ts?
Porque TypeScript sigue la cadena de imports automáticamente (compilación en cascada): detecta que
main.tsimporta decalculator.tsy lo compila también.
4. En mi proyecto, ¿por qué el import necesita .js al final si el archivo es .ts?
Porque mi
package.jsonraíz tiene"type": "module", que activa las reglas estrictas de ESM de Node — y ESM exige la extensión completa en los imports relativos. TypeScript compila igual sin ella, pero Node truena al ejecutar (ERR_MODULE_NOT_FOUND).
5. ¿Qué es un monolito, y qué problema trae?
Un archivo (o app) que concentra toda la lógica en un solo bloque. Cuantas más líneas tiene, más probable es que aparezca un bug y más difícil es encontrarlo.
6. ¿Puedo combinar varios import del mismo archivo en una sola línea?
Sí:
import { suma, resta, Pi } from "./calculator.js";trae varias cosas del mismo módulo en un soloimport, sin repetir la ruta.
7. ¿Qué pasa si intento importar algo que no tiene export en su archivo original?
Error de compilación
TS2459: "declares 'X' locally, but it is not exported". La función existe, pero queda encerrada en su archivo — nadie de afuera la puede usar.
8. ¿Cuál es la diferencia entre export default y un export nombrado (export const)?
Un archivo puede tener un solo
export default(se importa sin llaves), pero cuantos exports nombrados quiera (se importan con llaves, por nombre exacto o conas).
9. ¿Qué pasa si main.ts importa de b.ts, y b.ts a su vez importa de c.ts?
tsc main.tscompila los tres archivos de un tirón — la cascada sigue toda la cadena de imports, no solo el primer nivel.
10. ¿Cuándo conviene refactorizar un archivo grande a varios módulos?
Cuando ya funciona pero identificas bloques con una responsabilidad clara (o que se repiten). Ahí los mueves a archivos pequeños, los exportas, e importas solo lo necesario en el archivo principal — no hace falta modularizar desde el día uno.
📎 Apuntes relacionados
- Clase 8: Herencia (
extends) — compilación en cascada - Clase 9: Genéricos — por qué esta carpeta no tiene
tsconfig.json - Conceptos
- Comandos
➡️ Siguiente
Utility types — Partial, Pick, Omit, Record… transformar tipos existentes sin reescribirlos.