Skip to content

📙 Clase 12 — TypeScript compilado y ejecutado en el navegador

TypeScript · 2026-08-06 · Carpeta: 02-Ejercicios/Web ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Por qué el navegador no entiende .ts directamente y siempre hay que compilar a .js antes de usarlo en un HTML.
  • Cómo conectar un index.html con el JavaScript compilado usando <script src="...">.
  • Cómo previsualizar el HTML en vivo con Live Server o Live Preview (extensiones de VS Code) en vez de abrir el archivo a mano.
  • Cómo confirmar que el código llegó al navegador revisando la consola de DevTools (F12 → pestaña Console).
  • Tres gotchas reales que comprobé en el navegador: olvidar recompilar deja el sitio desactualizado, el orden de los <script> importa sin módulos, y usar import/export (de la Clase 11) directo en un <script> normal truena si no le agregas type="module".

📚 Definiciones clave

TérminoQué esEjemplo de esta clase
tsc archivo.tsCompila TypeScript a JavaScript — el navegador solo lee lo que sale de aquítsc main.ts → genera main.js
<script src="main.js">Le dice al HTML qué JavaScript cargar y ejecutarindex.html apunta a main.js, no a main.ts
Live Server / Live PreviewExtensión de VS Code que sirve tu HTML en una URL localhost con recarga automáticaBotón Go Live / icono Show Preview
DevTools ConsolePanel del navegador donde aparecen tus console.log()F12Console

📖 PARTE TEÓRICA

🌐 1. Por qué necesito compilar TypeScript antes de usarlo en HTML

El navegador no entiende TypeScript de forma nativa — solo lee JavaScript. Cualquier archivo .ts necesita pasar por el compilador antes de poder invocarse desde un HTML.

La lógica es simple: TypeScript existe para mejorar el tipado dentro de JavaScript, pero JavaScript es el lenguaje que en verdad corre en el navegador. Por eso el flujo siempre es el mismo: escribir en TypeScript → compilar → dejar que el HTML cargue el JavaScript resultante.

🧪 Entrevista: ¿Por qué el navegador no puede leer TypeScript directamente? Porque TypeScript es un superset de JavaScript que añade tipado estático — es una capa que solo existe en tiempo de compilación. Los navegadores únicamente ejecutan JavaScript, así que hace falta tsc para generar el .js que el HTML sí puede invocar.

🗂️ 2. Estructura del proyecto web: index.html + main.ts

Un proyecto web mínimo con TypeScript necesita dos archivos que trabajan en conjunto:

  • index.html — un HTML genérico que invoca el script compilado con <script src="main.js"></script>.
  • main.ts — el archivo TypeScript con la lógica (en el ejemplo, un console.log("Hola desde mi explorador"), la variante web del clásico hola mundo).
html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <script src="main.js"></script>
</head>
<body>
  <h1 class="title">Mi curso de Typescript en Platzi</h1>
  <label for="message">Escribe algo:</label>
  <input type="text" id="message" placeholder="Mensaje del usuario" />
</body>
</html>
typescript
// main.ts
console.log("Hola desde mi explorador");

💡 Fíjate en el detalle: el HTML llama a main.js, no a main.ts. Esa es la pista de que hace falta compilar antes de abrir el sitio — si el HTML pidiera main.ts directamente, el navegador simplemente no sabría qué hacer con él.

⚙️ 3. Compilar main.ts a main.js

Desde la terminal, parado en la carpeta del proyecto:

bash
cd 02-Ejercicios/Web
tsc main.ts

Si corres ls después, aparece main.js junto al original — en VS Code también se ve en el árbol de archivos. Ese archivo generado es el que el HTML va a leer sin problemas.

👀 4. Previsualizar el HTML directamente desde VS Code

Para ver el resultado en el navegador sin abrir el archivo manualmente (doble clic), conviene instalar una extensión que levante un servidor local. Dos opciones:

ExtensiónAutorCómo se activa
Live ServerRitwick Dey (+60M descargas)Botón Go Live en la barra inferior de VS Code
Live PreviewMicrosoftIcono Show Preview dentro del editor al abrir un .html

💡 ¿Qué hace Live Server? Levanta un servidor local que sirve tus archivos HTML en una URL tipo http://localhost, con recarga automática cuando guardas cambios. Es la forma más rápida de previsualizar un proyecto web estático — el mismo principio que usa este propio sitio de apuntes con npm run docs:dev (VitePress), solo que aquí es para HTML plano sin framework.

🔍 5. Verificar que TypeScript está funcionando en el navegador

Con la previsualización abierta, el siguiente paso es revisar la consola del explorador — ahí se confirma que el console.log escrito en TypeScript llegó hasta el navegador.

  • Dentro de Live Preview: abres el panel de herramientas de desarrollo desde el menú de la extensión y entras a la pestaña Console.
  • Fuera de VS Code: copias la URL de localhost, la pegas en tu navegador (Chrome, Edge, el que sea) y presionas F12 para abrir DevTools → pestaña Console.

En cualquiera de los dos casos, el resultado es idéntico: el mensaje aparece en consola.

Verificado en este equipo sirviendo 02-Ejercicios/Web/index.html en un localhost real y leyendo la consola del navegador:

Chrome DevTools · Console
Hola desde mi explorador

🧭 6. La cadena completa: de .ts a la pantalla

main.ts  --tsc-->  main.js  <--<script src>--  index.html  --navegador-->  Console (F12)
 (tipos)          (JS puro)      (lo invoca)                 (lo ejecuta)   (lo muestro)

Es el mismo console.log que antes corrías con node en la terminal (Clase 1), solo que ahora vive en un sitio web real: escribes en main.ts, compilas con tsc y se genera main.js, el index.html invoca ese main.js con la etiqueta <script>, y el navegador lo ejecuta y muestra el resultado en consola. Así es como TypeScript hereda funcionalidad a JavaScript, y JavaScript la representa gracias al HTML.


💻 PARTE PRÁCTICA

Archivos trabajados: 02-Ejercicios/Web/index.html y 02-Ejercicios/Web/main.ts

typescript
// main.ts
console.log("Hola desde mi explorador");
bash
cd 02-Ejercicios/Web
tsc main.ts

Salida real verificada — servido en localhost real y confirmado en la consola del navegador (no solo compilado, también ejecutado en Chrome de verdad):

zsh · Web
$ tsc main.ts
$ ls
index.html  main.js  main.ts

Todo verificado y corriendo. ✅


🏋️ EJERCICIOS CON SOLUCIÓN

Ejercicio 1 — Cambiar el mensaje y recompilar

Cambia el texto del console.log en main.ts por uno propio, recompílalo y confirma en la consola del navegador (con Live Server o F12) que el mensaje nuevo aparece.

💡 ¿Sabías que…? — el HTML nunca ve tu `.ts`

El HTML solo conoce el archivo que declaraste en <script src="..."> — en este caso main.js. Cambiar main.ts no actualiza automáticamente lo que ve el navegador: hace falta recompilar (tsc main.ts) para que el .js se regenere con el nuevo texto. Ejemplo de referencia:

typescript
// antes
console.log("Primera versión");
// después de editar y recompilar
console.log("Segunda versión, ya recompilada");
Ver solución
typescript
// main.ts
console.log("Hola, este es mi propio mensaje en el navegador");
bash
tsc main.ts

Consola del navegador tras recargar:

Chrome DevTools · Console
Hola, este es mi propio mensaje en el navegador

Ejercicio 2 — Variable tipada + template string

Declara una constante nombre: string y muéstrala en consola con un template string (repaso de la Clase 1).

💡 ¿Sabías que…? — el tipado no cambia el JS generado

const nombre: string = "algo" compila a const nombre = "algo"; — la anotación de tipo : string desaparece por completo en el .js. Sirve solo en tiempo de compilación, para que tsc te avise si intentas asignarle un número por error. Ejemplo de referencia:

typescript
const ciudad: string = "Lima";
console.log(`Vivo en ${ciudad}`);
Ver solución

Archivo real: 02-Ejercicios/Web/variable-tipada.ts

typescript
const nombre: string = "Styp";
console.log(`Hola, ${nombre} desde TypeScript`);

Verificado con tsc variable-tipada.ts + ejecución real:

Console
Hola, Styp desde TypeScript

Ejercicio 3 — Loguear varios tipos en un solo mensaje

Declara edad: number y activo: boolean, y muéstralos juntos en un solo console.log con template string.

💡 ¿Sabías que…? — el template string convierte todo a texto

Dentro de ${...}, un template string llama internamente a .toString() sobre cualquier valor — por eso number y boolean se insertan sin problema junto a texto, aunque sean tipos distintos. Ejemplo de referencia:

typescript
const precio: number = 49.9;
const disponible: boolean = false;
console.log(`Precio: ${precio}, Disponible: ${disponible}`);
Ver solución

Archivo real: 02-Ejercicios/Web/tipos-multiples.ts

typescript
const edad: number = 25;
const activo: boolean = true;
console.log(`Edad: ${edad}, Activo: ${activo}`);

Verificado con tsc tipos-multiples.ts + ejecución real:

Console
Edad: 25, Activo: true

Ejercicio 4 — Leer el valor del <input> del HTML

El index.html de la práctica ya trae <input type="text" id="message" />. Escribe un .ts que lo lea con document.getElementById y lo muestre en consola.

💡 ¿Sabías que…? — el DOM necesita un cast explícito

document.getElementById devuelve el tipo genérico HTMLElement | null — TypeScript no sabe de antemano que ese elemento es un <input>, así que no te deja leer .value directamente sin avisarte. Se resuelve con as HTMLInputElement (un type assertion: "confío en que este elemento es un input"). Ejemplo de referencia:

typescript
const campo = document.getElementById("buscador") as HTMLInputElement;
console.log(campo.value);
Ver solución

Archivos reales: 02-Ejercicios/Web/dom-input.html + dom-input.ts

typescript
// dom-input.ts
const input = document.getElementById("message") as HTMLInputElement;
console.log(input.value);

Verificado en Chrome real, sirviendo el HTML en un localhost (input con value="Hola Mundo" por defecto):

Chrome DevTools · Console
Hola Mundo

⚠️ Si el <script> va en el <head> sin defer, el JS corre antes de que el <input> exista en el DOM y getElementById devuelve null — truena al leer .value. Por eso este archivo agrega defer al <script src="dom-input.js" defer>.

Ejercicio 5 — Cambiar el <h1> desde TypeScript

Usa document.querySelector para tomar el <h1 class="title"> del HTML y cambiar su texto con .textContent.

💡 ¿Sabías que…? — `querySelector` usa sintaxis CSS

document.querySelector acepta cualquier selector CSS (.clase, #id, tag), a diferencia de getElementById que solo busca por id. Igual que en el ejercicio 4, hace falta un cast porque TypeScript no sabe qué tipo exacto de elemento vas a encontrar. Ejemplo de referencia:

typescript
const parrafo = document.querySelector("p.descripcion") as HTMLParagraphElement;
parrafo.textContent = "Texto actualizado";
Ver solución

Archivos reales: 02-Ejercicios/Web/titulo-dinamico.html + titulo-dinamico.ts

typescript
// titulo-dinamico.ts
const titulo = document.querySelector(".title") as HTMLHeadingElement;
titulo.textContent = "Título cambiado desde TypeScript";

Verificado en Chrome real — el <h1> cambia de "Mi curso de Typescript en Platzi" a:

DOM · h1.title
Título cambiado desde TypeScript

Ejercicio 6 — Función tipada que usa el valor del input

Crea function saludar(nombre: string): string (repaso de la Clase 4) que arme un saludo, y úsala con el valor del <input> para loguear el resultado.

💡 ¿Sabías que…? — una función tipada no sabe de dónde viene el dato

saludar(nombre: string) no le importa si nombre viene de un input, una constante o un array — solo exige que sea string. Por eso combinar DOM + funciones tipadas es tan directo: lees el .value (que ya es string), y se lo pasas tal cual. Ejemplo de referencia:

typescript
function despedir(nombre: string): string {
  return `Adiós, ${nombre}, vuelve pronto`;
}
console.log(despedir("visitante"));
Ver solución

Archivos reales: 02-Ejercicios/Web/saludo-funcion.html + saludo-funcion.ts

typescript
// saludo-funcion.ts
function saludar(nombre: string): string {
  return `¡Hola, ${nombre}! Bienvenido a TypeScript en el navegador`;
}

const input = document.getElementById("message") as HTMLInputElement;
console.log(saludar(input.value));

Verificado en Chrome real (input con value="Styp"):

Chrome DevTools · Console
¡Hola, Styp! Bienvenido a TypeScript en el navegador

Ejercicio 7 — Forzar el desfase de olvidar recompilar

Antes de correrlo: si editas main.ts pero no vuelves a correr tsc, y recargas el navegador, ¿qué mensaje esperas ver? Anticipa tu respuesta y compruébalo.

💡 ¿Sabías que…? — el navegador nunca lee `.ts`, ni por accidente

El <script> apunta siempre al .js compilado — el navegador no tiene forma de saber que el .ts cambió, porque ni siquiera lo carga. Mientras no corras tsc de nuevo, vas a seguir viendo la versión vieja del .js, sin importar cuántas veces recargues la página o guardes el .ts. Es la razón por la que Live Server con recarga automática NO reemplaza a recompilar: recarga el HTML, no regenera el JavaScript.

Ver solución

Verificado en terminal + Chrome real, paso a paso:

bash
echo 'console.log("Versión A");' > demo.ts
tsc demo.ts        # genera demo.js con "Versión A"
# ... abres demo.html en el navegador → consola muestra "Versión A"

echo 'console.log("Versión B - cambiada");' > demo.ts   # editas el .ts, SIN recompilar
# ... recargas el navegador → consola SIGUE mostrando "Versión A" (el .js no cambió)

tsc demo.ts         # ahora sí recompilas
# ... recargas de nuevo → consola muestra "Versión B - cambiada"
Chrome DevTools · Console
Versión A          ← tras editar el .ts SIN recompilar, sigue igual
Versión B - cambiada  ← recién ahora, tras `tsc demo.ts`

⚠️ Este es el bug más común al empezar con TS en el navegador: "hice el cambio y no se ve nada" casi siempre significa te faltó recompilar.

Ejercicio 8 — Orden de <script> sin módulos

Crea uno.ts con const mensajeGlobal = "..." y dos.ts con console.log(mensajeGlobal) (ninguno usa export/import). Colócalos en el HTML en ambos órdenes y observa qué pasa en cada caso.

💡 ¿Sabías que…? — sin módulos, los scripts comparten un solo scope global

Sin export/import (visto en la Clase 11), varios <script> en un mismo HTML se ejecutan en el orden en que aparecen, y todos comparten el mismo scope global — por eso un script posterior puede usar una variable declarada en uno anterior. Pero si el orden se invierte, la variable todavía no existe cuando el segundo script corre. Ejemplo de referencia:

typescript
// base.ts
const version = "2.0";
typescript
// app.ts
console.log(`Versión: ${version}`); // solo funciona si base.js carga ANTES
Ver solución
typescript
// uno.ts
const mensajeGlobal = "Vengo del primer script";
typescript
// dos.ts
console.log(mensajeGlobal);
html
<!-- orden CORRECTO -->
<script src="uno.js"></script>
<script src="dos.js"></script>
html
<!-- orden INCORRECTO -->
<script src="dos.js"></script>
<script src="uno.js"></script>

Verificado en Chrome real, ambos casos:

Chrome DevTools · Console
-- orden correcto (uno.js, luego dos.js) --
Vengo del primer script
-- orden incorrecto (dos.js, luego uno.js) --
Uncaught ReferenceError: mensajeGlobal is not defined

💡 Este es exactamente el problema que los módulos (export/import, Clase 11) resuelven de raíz: cada archivo declara explícitamente qué necesita de dónde, en vez de depender del orden físico de las etiquetas <script>.

Ejercicio 9 — Intentar usar import/export sin type="module"

Toma un archivo compilado que use import/export (como los de la Clase 11) y cárgalo con un <script src="..."> normal, sin type="module". Anticipa el error y verifícalo.

💡 ¿Sabías que…? — `type="module"` no es opcional para ES Modules en el navegador

Un <script> normal se ejecuta como script clásico: el navegador espera JavaScript "plano", sin import/export de nivel superior. Para que el navegador entienda esa sintaxis (los ES Modules nativos), el <script> necesita el atributo type="module" — sin él, la etiqueta import es simplemente sintaxis inválida para ese contexto. Ejemplo de referencia:

html
<!-- así SÍ entiende el import -->
<script src="app.js" type="module"></script>
Ver solución
typescript
// config-modulo.ts
export const version = "1.0.0";
typescript
// modulo-error.ts
import { version } from "./config-modulo.js";
console.log(version);
html
<!-- sin type="module": esto truena -->
<script src="modulo-error.js" defer></script>

Verificado en Chrome real:

Chrome DevTools · Console
Uncaught SyntaxError: Cannot use import statement outside a module

Agregando type="module" al <script> (<script src="modulo-error.js" type="module">), el mismo archivo corre sin problema:

Chrome DevTools · Console
1.0.0

⚠️ En proyectos reales casi nunca se agrega type="module" a mano en varios <script> sueltos — se usa un bundler (Vite, Webpack, esbuild…) que empaqueta todos los módulos en un solo archivo optimizado. Pero entender por qué truena sin type="module" ayuda a leer el error el día que aparezca.

Ejercicio 10 — Integrador: mini formulario con validación

Arma un HTML con <input id="message">, un <button id="enviar"> y un <p id="resultado">. Al hacer clic en el botón, valida que el input no esté vacío y muestra el resultado tanto en consola como en el <p>.

💡 ¿Sabías que…? — `addEventListener` conecta TypeScript con la interacción del usuario

Todo lo visto hasta acá (compilar, leer el DOM, funciones tipadas) se combina aquí con addEventListener("click", ...): en vez de correr el código una sola vez al cargar la página, lo dejas esperando a que el usuario interactúe. Ejemplo de referencia:

typescript
const boton = document.getElementById("calcular") as HTMLButtonElement;
boton.addEventListener("click", () => {
  console.log("Se hizo clic");
});
Ver solución

Archivos reales: 02-Ejercicios/Web/formulario.html + formulario.ts

typescript
// formulario.ts
function validarMensaje(valor: string): string {
  if (valor.trim() === "") {
    return "⚠️ El mensaje no puede estar vacío";
  }
  return `✅ Mensaje recibido: ${valor}`;
}

const input = document.getElementById("message") as HTMLInputElement;
const boton = document.getElementById("enviar") as HTMLButtonElement;
const resultado = document.getElementById("resultado") as HTMLParagraphElement;

boton.addEventListener("click", () => {
  const mensaje = validarMensaje(input.value);
  console.log(mensaje);
  resultado.textContent = mensaje;
});

Verificado en Chrome real, ambos casos (clic con texto y clic con input vacío):

Chrome DevTools · Console
✅ Mensaje recibido: Styp
⚠️ El mensaje no puede estar vacío

💡 Este ejercicio junta todo el recorrido de la clase: tsc para compilar, DOM para leer/escribir, funciones tipadas para la lógica, y eventos para responder al usuario — el mismo patrón detrás de cualquier formulario web real.

❓ Preguntas y respuestas (autoevaluación)

1. ¿Por qué el navegador no puede ejecutar un archivo .ts directamente?

Porque los navegadores solo entienden JavaScript. TypeScript es un superset que añade tipado estático, pero esa capa solo existe en tiempo de compilación — hay que convertirlo a .js con tsc antes de que un HTML pueda invocarlo.

2. En el index.html de esta clase, ¿por qué el <script src> apunta a main.js y no a main.ts?

Porque el navegador solo puede cargar JavaScript. main.js es el archivo generado por tsc main.ts — el HTML nunca ve ni necesita el .ts original.

3. ¿Qué comando compila main.ts a main.js?

tsc main.ts, corrido desde la carpeta donde está el archivo.

4. ¿Qué hace una extensión como Live Server?

Levanta un servidor local que sirve tus archivos HTML en una URL http://localhost, con recarga automática al guardar cambios — evita tener que abrir el HTML a mano con doble clic.

5. ¿Dónde reviso si mi console.log de TypeScript llegó al navegador?

En la consola de DevTools: F12 (o el menú de Live Preview) → pestaña Console.

6. Si edito main.ts pero no vuelvo a correr tsc, ¿qué va a mostrar el navegador al recargar?

El mensaje viejo. El <script> apunta al .js compilado, no al .ts — sin recompilar, el .js no cambia, sin importar cuántas veces recargues la página.

7. ¿Por qué document.getElementById("message") necesita un as HTMLInputElement para poder leer .value?

Porque getElementById devuelve el tipo genérico HTMLElement | null — TypeScript no sabe de antemano qué tipo específico de elemento es. El as es un type assertion que le dice al compilador "confío en que esto es un <input>".

8. Si tengo dos <script> sin export/import, y el segundo usa una variable declarada en el primero, ¿qué pasa si invierto el orden de las etiquetas?

ReferenceError: <variable> is not defined. Sin módulos, los scripts comparten un solo scope global y se ejecutan en el orden físico en que aparecen en el HTML — si el que usa la variable carga antes que el que la declara, todavía no existe.

9. ¿Qué error da el navegador si cargo un .js compilado con import/export en un <script> normal, sin type="module"?

Uncaught SyntaxError: Cannot use import statement outside a module. Un <script> normal se ejecuta como script clásico, que no entiende la sintaxis de ES Modules — hace falta agregar type="module" a la etiqueta para que funcione.

10. ¿Cuál es la cadena completa desde que escribo código hasta que lo veo en el navegador?

main.ts (escribo con tipos) → tsc main.ts (compilo) → main.js (JS puro) → index.html lo invoca con <script src="main.js"> → el navegador lo ejecuta → el resultado aparece en la consola de DevTools (F12).

📎 Apuntes relacionados

➡️ Siguiente

Utility typesPartial, Pick, Omit, Record… transformar tipos existentes sin reescribirlos.