Tu Primer Script Completo en JavaScript
Objetivos de aprendizajeAl finalizar esta lección serás capaz de:
- Crear un archivo HTML que cargue y ejecute JavaScript correctamente
- Entender la diferencia entre JavaScript inline, interno y externo
- Construir un programa interactivo que combine todos los conceptos del módulo
- Estructurar tu código de forma legible y mantenible
- Desplegar tu primer mini-proyecto web funcional
1. Conectando JavaScript con HTML
Hasta ahora escribimos JavaScript en la consola del navegador o en archivos .js ejecutados con Node.js. Pero el uso más común de JavaScript es en páginas web, conectándolo con HTML.
Las 3 formas de incluir JavaScript en HTML
Forma 1: JavaScript inline (NO recomendado)
<!-- Directamente en atributos de HTML -->
<button onclick="alert('¡Hola!');">Hacé click</button>
<a href="javascript:void(0)" onclick="console.log('click');">Link</a>
¿Por qué NO se recomienda? Mezcla la estructura (HTML) con el comportamiento (JavaScript), haciendo el código difícil de mantener. Además, tiene limitaciones de seguridad.
Forma 2: JavaScript interno (script tag en HTML)
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi Página</title>
</head>
<body>
<h1 id="titulo">Hola Mundo</h1>
<script>
// JavaScript directamente en el HTML
const titulo = document.getElementById("titulo");
titulo.style.color = "blue";
console.log("JavaScript interno ejecutado");
</script>
</body>
</html>
Aceptable para scripts pequeños y demos rápidas, pero para proyectos reales preferimos archivos externos.
Forma 3: JavaScript externo (RECOMENDADO)
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi Página</title>
</head>
<body>
<h1 id="titulo">Hola Mundo</h1>
<p id="parrafo">Este texto va a cambiar.</p>
<button id="boton">Hacé click</button>
<!-- Script externo al final del body -->
<script src="app.js"></script>
</body>
</html>
// app.js (archivo separado)
const titulo = document.getElementById("titulo");
const parrafo = document.getElementById("parrafo");
const boton = document.getElementById("boton");
titulo.style.color = "#6366f1";
boton.addEventListener("click", () => {
parrafo.textContent = "¡El texto cambió!";
});
¿Por qué al final del <body>?
Cuando el navegador lee un HTML, lo procesa de arriba a abajo. Si ponés el <script> en el <head>, el JavaScript se ejecuta antes de que existan los elementos HTML, y tu código no encontrará los elementos que necesita.
<!-- ❌ PROBLEMA: el script se ejecuta antes de que exista #titulo -->
<head>
<script>
document.getElementById("titulo").textContent = "Nuevo título";
// TypeError: Cannot set properties of null
</script>
</head>
<body>
<h1 id="titulo">Hola</h1>
</body>
Soluciones:
<!-- ✅ Solución 1: Script al final del body -->
<body>
<h1 id="titulo">Hola</h1>
<script src="app.js"></script>
</body>
<!-- ✅ Solución 2: Atributo defer (carga en paralelo, ejecuta después) -->
<head>
<script src="app.js" defer></script>
</head>
<body>
<h1 id="titulo">Hola</h1>
</body>
<!-- ✅ Solución 3: DOMContentLoaded event -->
<head>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("titulo").textContent = "Nuevo título";
});
</script>
</head>
La opción más moderna y recomendada es usar defer o poner los scripts al final del body.
2. Interactuando con la página: document object model (DOM) básico
El DOM (Document Object Model) es la representación de tu HTML como un árbol de objetos que JavaScript puede manipular. Cada etiqueta HTML se convierte en un "nodo" del DOM.
Seleccionando elementos
// Por ID (devuelve 1 elemento)
const titulo = document.getElementById("titulo");
// Por clase (devuelve una colección)
const items = document.getElementsByClassName("item");
// Por selector CSS (devuelve el primero que encuentre)
const primer = document.querySelector(".item");
const boton = document.querySelector("#boton-enviar");
const link = document.querySelector("a[href='#contacto']");
// Todos los que coincidan con el selector
const todosLosItems = document.querySelectorAll(".item");
// Devuelve un NodeList (similar a un array)
todosLosItems.forEach(item => {
console.log(item.textContent);
});
Modificando elementos
const elemento = document.querySelector("#mi-elemento");
// Cambiar texto
elemento.textContent = "Nuevo texto";
elemento.innerHTML = "<strong>Texto en negrita</strong>";
// Cambiar estilos
elemento.style.color = "red";
elemento.style.backgroundColor = "#f0f0f0";
elemento.style.padding = "10px";
elemento.style.borderRadius = "8px";
// Agregar/quitar clases CSS
elemento.classList.add("activo");
elemento.classList.remove("oculto");
elemento.classList.toggle("seleccionado"); // Agrega si no tiene, quita si tiene
// Cambiar atributos
elemento.setAttribute("data-id", "123");
const valor = elemento.getAttribute("data-id"); // "123"
Creando elementos nuevos
// Crear un nuevo párrafo
const nuevoParrafo = document.createElement("p");
nuevoParrafo.textContent = "Este párrafo fue creado con JavaScript";
nuevoParrafo.classList.add("destacado");
// Agregarlo al documento
const contenedor = document.querySelector("#contenedor");
contenedor.appendChild(nuevoParrafo);
// Crear una lista completa
const lista = document.createElement("ul");
const frutas = ["Manzana", "Banana", "Cereza"];
frutas.forEach(fruta => {
const li = document.createElement("li");
li.textContent = fruta;
lista.appendChild(li);
});
contenedor.appendChild(lista);
Escuchando eventos
const boton = document.querySelector("#mi-boton");
const input = document.querySelector("#mi-input");
// Click
boton.addEventListener("click", () => {
console.log("¡Botón clickeado!");
});
// Input (cada vez que se escribe)
input.addEventListener("input", (evento) => {
console.log("Escribiendo:", evento.target.value);
});
// Submit de formulario
const formulario = document.querySelector("#mi-form");
formulario.addEventListener("submit", (evento) => {
evento.preventDefault(); // Prevenir recarga de página
console.log("Formulario enviado");
});
// Teclado
document.addEventListener("keydown", (evento) => {
console.log("Tecla presionada:", evento.key);
if (evento.key === "Escape") {
console.log("¡Escape presionado!");
}
});
3. Proyecto completo: Generador de contraseñas
Vamos a crear un proyecto completo que combina todo lo que aprendimos en este módulo: variables, operadores, funciones, DOM y eventos.
El HTML (index.html)
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generador de Contraseñas</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
background: #1a1a2e;
color: #e0e0e0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background: #16213e;
padding: 2rem;
border-radius: 16px;
width: 400px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 { text-align: center; margin-bottom: 1.5rem; color: #6366f1; }
.password-display {
background: #0f3460;
padding: 1rem;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 1.3rem;
text-align: center;
letter-spacing: 2px;
margin-bottom: 1rem;
word-break: break-all;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.option { display: flex; justify-content: space-between; align-items: center; padding: 0.5rem 0; }
.option label { cursor: pointer; }
input[type="range"] { width: 60%; accent-color: #6366f1; }
input[type="checkbox"] { accent-color: #6366f1; width: 20px; height: 20px; }
.btn {
width: 100%;
padding: 0.8rem;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
margin-top: 1rem;
font-weight: bold;
transition: transform 0.1s;
}
.btn:active { transform: scale(0.98); }
.btn-generate { background: #6366f1; color: white; }
.btn-copy { background: #10b981; color: white; }
.strength { text-align: center; margin-top: 0.5rem; font-size: 0.9rem; }
.strength.weak { color: #ef4444; }
.strength.medium { color: #f59e0b; }
.strength.strong { color: #10b981; }
</style>
</head>
<body>
<div class="container">
<h1>Generador de Contraseñas</h1>
<div class="password-display" id="passwordDisplay">
Hacé click en Generar
</div>
<div class="option">
<label for="length">Longitud: <span id="lengthValue">12</span></label>
<input type="range" id="length" min="4" max="32" value="12">
</div>
<div class="option">
<label for="uppercase">Mayúsculas (A-Z)</label>
<input type="checkbox" id="uppercase" checked>
</div>
<div class="option">
<label for="lowercase">Minúsculas (a-z)</label>
<input type="checkbox" id="lowercase" checked>
</div>
<div class="option">
<label for="numbers">Números (0-9)</label>
<input type="checkbox" id="numbers" checked>
</div>
<div class="option">
<label for="symbols">Símbolos (!@#$...)</label>
<input type="checkbox" id="symbols">
</div>
<button class="btn btn-generate" id="generateBtn">Generar Contraseña</button>
<button class="btn btn-copy" id="copyBtn">Copiar al Portapapeles</button>
<div class="strength" id="strength"></div>
</div>
<script src="app.js"></script>
</body>
</html>
El JavaScript (app.js)
// ============================================
// GENERADOR DE CONTRASEÑAS
// Proyecto del Módulo 1: Fundamentos de JS
// ============================================
// 1. SELECCIONAR ELEMENTOS DEL DOM
const passwordDisplay = document.getElementById("passwordDisplay");
const lengthSlider = document.getElementById("length");
const lengthValue = document.getElementById("lengthValue");
const uppercaseCheck = document.getElementById("uppercase");
const lowercaseCheck = document.getElementById("lowercase");
const numbersCheck = document.getElementById("numbers");
const symbolsCheck = document.getElementById("symbols");
const generateBtn = document.getElementById("generateBtn");
const copyBtn = document.getElementById("copyBtn");
const strengthDisplay = document.getElementById("strength");
// 2. DEFINIR LOS CONJUNTOS DE CARACTERES
const CHARS_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const CHARS_LOWER = "abcdefghijklmnopqrstuvwxyz";
const CHARS_NUMBERS = "0123456789";
const CHARS_SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?";
// 3. FUNCIÓN PRINCIPAL: GENERAR CONTRASEÑA
function generarPassword() {
const longitud = parseInt(lengthSlider.value);
let caracteres = "";
// Construir el conjunto de caracteres según opciones seleccionadas
if (uppercaseCheck.checked) caracteres += CHARS_UPPER;
if (lowercaseCheck.checked) caracteres += CHARS_LOWER;
if (numbersCheck.checked) caracteres += CHARS_NUMBERS;
if (symbolsCheck.checked) caracteres += CHARS_SYMBOLS;
// Validar que al menos una opción esté seleccionada
if (caracteres === "") {
passwordDisplay.textContent = "Seleccioná al menos una opción";
strengthDisplay.textContent = "";
return;
}
// Generar la contraseña caracter por caracter
let password = "";
for (let i = 0; i < longitud; i++) {
const indiceAleatorio = Math.floor(Math.random() * caracteres.length);
password += caracteres[indiceAleatorio];
}
// Mostrar la contraseña
passwordDisplay.textContent = password;
// Evaluar y mostrar la fortaleza
evaluarFortaleza(password);
console.log(`Contraseña generada: ${longitud} caracteres, ${caracteres.length} posibles`);
}
// 4. FUNCIÓN: EVALUAR FORTALEZA
function evaluarFortaleza(password) {
let puntaje = 0;
// Longitud
if (password.length >= 8) puntaje++;
if (password.length >= 12) puntaje++;
if (password.length >= 16) puntaje++;
// Variedad de caracteres
if (/[A-Z]/.test(password)) puntaje++;
if (/[a-z]/.test(password)) puntaje++;
if (/[0-9]/.test(password)) puntaje++;
if (/[^A-Za-z0-9]/.test(password)) puntaje++;
// Mostrar resultado
if (puntaje <= 3) {
strengthDisplay.textContent = "Fortaleza: Débil";
strengthDisplay.className = "strength weak";
} else if (puntaje <= 5) {
strengthDisplay.textContent = "Fortaleza: Media";
strengthDisplay.className = "strength medium";
} else {
strengthDisplay.textContent = "Fortaleza: Fuerte";
strengthDisplay.className = "strength strong";
}
}
// 5. FUNCIÓN: COPIAR AL PORTAPAPELES
async function copiarPassword() {
const password = passwordDisplay.textContent;
if (password === "Hacé click en Generar" || password === "Seleccioná al menos una opción") {
return;
}
try {
await navigator.clipboard.writeText(password);
copyBtn.textContent = "¡Copiado!";
setTimeout(() => {
copyBtn.textContent = "Copiar al Portapapeles";
}, 2000);
} catch (err) {
console.error("Error al copiar:", err);
}
}
// 6. CONECTAR EVENTOS
lengthSlider.addEventListener("input", () => {
lengthValue.textContent = lengthSlider.value;
});
generateBtn.addEventListener("click", generarPassword);
copyBtn.addEventListener("click", copiarPassword);
// Generar una contraseña inicial
generarPassword();
console.log("Generador de contraseñas cargado correctamente");
Este proyecto demuestra:
- Variables y constantes (
const,let) - Tipos de datos (strings, numbers, booleans)
- Operadores (aritméticos, comparación, lógicos, concatenación)
- console.log() para debugging
- Funciones (declaración, parámetros, retorno)
- DOM manipulation (seleccionar, modificar, crear elementos)
- Eventos (click, input)
- Condicionales (if/else)
- Loops (for)
Errores comunes de principiantes
Poner el
<script>en el<head>sindefer: El JavaScript se ejecuta antes de que el HTML esté listo, causando errores de null.Usar
innerHTMLcuandotextContentalcanza:innerHTMLpuede ser un riesgo de seguridad (XSS). UsátextContentpara texto plano.No usar
preventDefault()en formularios: Sin esto, el formulario recarga la página y tu JavaScript se pierde.Olvidar que
querySelectorAlldevuelve un NodeList, no un Array: Podés usarforEachpero nomapofilterdirectamente. Convertilo conArray.from().No separar HTML, CSS y JavaScript: Mantené cada lenguaje en su propio archivo para código más limpio y mantenible.
Puntos clave de esta lección
- Siempre usá archivos JavaScript externos (
<script src="app.js">) para proyectos reales. - Poné el
<script>al final del<body>o usá el atributodeferen el<head>. - El DOM es la representación de tu HTML que JavaScript puede manipular.
querySelector()yquerySelectorAll()son los métodos modernos para seleccionar elementos.addEventListener()es la forma correcta de manejar eventos (no usar atributos HTML inline).textContentpara texto plano,innerHTMLsolo cuando necesitás insertar HTML.- Un proyecto completo combina todos los fundamentos: variables, operadores, funciones, DOM y eventos.
Quiz de autoevaluación
1. ¿Cuál es la forma recomendada de incluir JavaScript en HTML?
a) JavaScript inline en atributos onclick
b) Script tag en el head sin defer
c) Archivo externo con script src al final del body o con defer
d) Escribirlo directamente en el CSS
2. ¿Qué hace document.querySelector('.item')?
a) Selecciona todos los elementos con clase 'item'
b) Selecciona el primer elemento que coincide con el selector CSS
c) Crea un nuevo elemento con clase 'item'
d) Elimina todos los elementos con clase 'item'
3. ¿Qué hace evento.preventDefault() en un formulario?
a) Previene que se ejecute JavaScript
b) Previene que la página se recargue al enviar el formulario
c) Elimina el evento
d) Previene errores de JavaScript
4. ¿Cuál es la diferencia entre textContent e innerHTML?
a) No hay diferencia
b) textContent inserta HTML, innerHTML inserta texto
c) textContent inserta texto plano, innerHTML puede insertar HTML
d) innerHTML es más seguro
5. ¿Qué atributo permite cargar un script en el head sin bloquear el HTML?
a) async
b) defer
c) lazy
d) preload
Respuestas: 1-c, 2-b, 3-b, 4-c, 5-b
Revisemos los puntos más importantes de esta lección antes de continuar.
Ejercicio práctico
Misión: Conversor de unidades
Creá una página web completa (HTML + CSS + JavaScript) que sea un conversor de unidades con las siguientes características:
- Un campo de input numérico para ingresar el valor
- Un selector (
<select>) para elegir la conversión: km→millas, °C→°F, kg→libras - Un botón para convertir
- Un área donde se muestra el resultado
- El resultado debe mostrarse con 2 decimales
- Si el input está vacío o no es un número, mostrar un mensaje de error
- Bonus: agregar un botón para invertir la conversión (millas→km, °F→°C, libras→kg)
Fórmulas:
- km a millas:
km * 0.621371 - °C a °F:
(celsius * 9/5) + 32 - kg a libras:
kg * 2.20462
Este ejercicio combina TODO lo que aprendiste en el Módulo 1. ¡Éxitos!
- Comprendo el concepto principal y puedo explicarlo con mis palabras
- Entiendo cómo aplicarlo en mi situación específica
- Necesito repasar algunas partes antes de continuar
- Quiero ver más ejemplos prácticos del tema