← Volver al Blog DevBlog
HTML5 CSS3 JavaScript PHP 8 MySQL

Desarrollo Web Vanilla

Guía completa para crear sitios web profesionales sin frameworks usando HTML, CSS, JavaScript, PHP y MySQL

📖 Introducción

En un mundo dominado por frameworks y librerías, dominar las tecnologías fundamentales del desarrollo web es más valioso que nunca.

¿Por qué aprender Vanilla?

Mayor control: Entiendes exactamente qué sucede.
Mejor rendimiento: Sin overhead de frameworks.
Depuración fácil: Menos abstracciones.
Fundamento sólido: Los fundamentos permanecen.

🏗️ HTML5

Estructura Básica

index.html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mi Página Web</title>
    <link rel="stylesheet" href="estilos.css">
</head>
<body>
    <!-- Contenido visible -->
    <script src="script.js"></script>
</body>
</html>

Formularios

formulario.html
<form action="procesar.php" method="POST">
    <div>
        <label for="nombre">Nombre:</label>
        <input type="text" id="nombre" name="nombre" required>
    </div>

    <div>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required>
    </div>

    <div>
        <label for="mensaje">Mensaje:</label>
        <textarea id="mensaje" name="mensaje" rows="4"></textarea>
    </div>

    <button type="submit">Enviar</button>
</form>

🎨 CSS3

Selectores

estilos.css
/* Selector de elemento */
p {
    color: #333;
    font-size: 16px;
}

/* Selector de clase */
.destacado {
    font-weight: bold;
    color: #e74c3c;
}

/* Selector de ID */
#header {
    background: #2c3e50;
    padding: 20px;
}

/* Selector descendente */
article p {
    line-height: 1.8;
}

/* Múltiples selectores */
h1, h2, h3 {
    font-family: Arial, sans-serif;
}

Box Model

box-model.css
.caja {
    /* Contenido */
    width: 300px;
    height: 200px;

    /* Espacio interno */
    padding: 20px;

    /* Borde */
    border: 2px solid #333;

    /* Espacio externo */
    margin: 10px;
}

/* box-sizing: border-box incluye padding y border */
* {
    box-sizing: border-box;
}

Flexbox

flexbox.css
/* Contenedor flex */
.contenedor {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
}

/* Item flex */
.item {
    flex: 1;
    flex-shrink: 0;
    flex-basis: 200px;
}

/* Responsive */
@media (min-width: 768px) {
    .contenedor {
        flex-direction: column;
    }
}

Variables CSS

variables.css
/* Definir variables */
:root {
    --color-primario: #3498db;
    --color-secundario: #2ecc71;
    --espaciado: 16px;
    --sombra: 0 4px 6px rgba(0,0,0,0.1);
}

.boton {
    background: var(--color-primario);
    padding: var(--espaciado);
    box-shadow: var(--sombra);
}

JavaScript

Fundamentos

fundamentos.js
// Variables
let nombre = "Juan";
const PI = 3.1416;
var edad = 25;

// Tipos de datos
let texto   = "Hola";
let numero  = 42;
let boolean = true;
let array   = [1, 2, 3];
let objeto  = {clave: "valor"};

// Condicionales
if (edad >= 18) {
    console.log("Mayor de edad");
} else if (edad >= 13) {
    console.log("Adolescente");
} else {
    console.log("Niño");
}

// Operador ternario
let tipo = edad >= 18 ? "adulto" : "menor";

Funciones

funciones.js
// Función tradicional
function saludar(nombre) {
    return "Hola, " + nombre;
}

// Función flecha (arrow)
const saludarFlecha = (nombre) => {
    return `Hola, ${nombre}`;
};

// Parámetros por defecto
function crearUsuario(nombre, rol = "usuario") {
    return { nombre, rol };
}

// Async/await
async function fetchData() {
    const res = await fetch("/api/data");
    return await res.json();
}

Arrays y Métodos

arrays.js
const numeros = [1, 2, 3, 4, 5];

// map - transformar
const dobles = numeros.map(n => n * 2);        // [2,4,6,8,10]

// filter - filtrar
const pares = numeros.filter(n => n % 2 === 0);  // [2,4]

// find - encontrar primero
const encontrado = numeros.find(n => n > 3);      // 4

// reduce - acumular
const suma = numeros.reduce((acc, n) => acc + n, 0);  // 15

// some y every
const hayPar = numeros.some(n => n % 2 === 0);   // true
const todosPares = numeros.every(n => n % 2 === 0); // false

Manipulación del DOM

dom.js
// Seleccionar elementos
const elem  = document.getElementById("miId");
const elems = document.querySelectorAll(".clase");

// Modificar contenido
elem.textContent = "Nuevo texto";
elem.innerHTML = "<strong>HTML</strong>";

// Modificar clases
elem.classList.add("activo");
elem.classList.remove("oculto");
elem.classList.toggle("visible");

// Crear elemento
const div = document.createElement("div");
div.textContent = "Nuevo";
elem.appendChild(div);

Fetch API

fetch.js
// GET - obtener datos
fetch("https://api.ejemplo.com/users")
    .then(res => res.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));

// POST - enviar datos
fetch("https://api.ejemplo.com/users", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        nombre: "Juan",
        email: "juan@ejemplo.com"
    })
})
    .then(res => res.json())
    .then(data => console.log(data));

🐘 PHP

Fundamentos

basico.php
<?php
// Variables
$nombre = "Juan";
$edad    = 25;
$activo  = true;

// Arrays
$frutas = ["manzana", "pera", "uva"];
$usuario = [
    "nombre" => "Juan",
    "email"  => "juan@ejemplo.com"
];

// Condicionales
if ($edad >= 18) {
    echo "Mayor de edad";
} else {
    echo "Menor de edad";
}

// Null coalescing (PHP 7+)
$valor = $_GET["id"] ?? "default";
?>

Funciones

funciones.php
<?php
// Función básica
function saludar($nombre) {
    return "Hola, $nombre";
}

// Tipado y valor por defecto
function sumar(int $a, int $b = 0): int {
    return $a + $b;
}

// Arrow function (PHP 7.4+)
$doble = fn($x) => $x * 2;

// Argumentos variables
function sumarTodo(...$numeros) {
    return array_sum($numeros);
}
?>

Clases y Objetos

clases.php
<?php
class Usuario {
    private $nombre;
    private $email;

    public function __construct($nombre, $email) {
        $this->nombre = $nombre;
        $this->email = $email;
    }

    public function getNombre(): string {
        return $this->nombre;
    }

    public function setNombre($nombre): void {
        $this->nombre = $nombre;
    }
}

// Uso
$usuario = new Usuario("Juan", "juan@email.com");
echo $usuario->getNombre();
?>

MySQL con PDO

database.php
<?php
// Conexión PDO
try {
    $pdo = new PDO(
        "mysql:host=localhost;dbname=mi_db;charset=utf8mb4",
        "usuario",
        "contraseña",
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );
} catch (PDOException $e) {
    die("Error: " . $e->getMessage());
}

// Prepared statement - SELECT
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE id = ?");
$stmt->execute([$id]);
$usuario = $stmt->fetch();

// Prepared statement - INSERT
$stmt = $pdo->prepare(
    "INSERT INTO usuarios (nombre, email) VALUES (?, ?)"
);
$stmt->execute([$nombre, $email]);
$nuevoId = $pdo->lastInsertId();
?>

🗄️ MySQL

Crear Tablas

schema.sql
CREATE DATABASE mi_aplicacion
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

USE mi_aplicacion;

CREATE TABLE usuarios (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    nombre        VARCHAR(100) NOT NULL,
    email         VARCHAR(255) NOT NULL UNIQUE,
    password      VARCHAR(255) NOT NULL,
    rol           ENUM('usuario','admin') DEFAULT 'usuario',
    creado_en     TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

CREATE TABLE posts (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    usuario_id   INT NOT NULL,
    titulo        VARCHAR(200) NOT NULL,
    contenido     TEXT,
    FOREIGN KEY (usuario_id)
      REFERENCES usuarios(id)
);

CREATE INDEX idx_email ON usuarios(email);

Consultas Básicas (CRUD)

consultas.sql
-- INSERT - Crear
INSERT INTO usuarios (nombre, email, password)
VALUES ('Juan', 'juan@email.com', 'hash_password');

-- SELECT - Leer
SELECT * FROM usuarios WHERE activo = TRUE;
SELECT * FROM usuarios WHERE email LIKE '%@gmail.com';
SELECT * FROM usuarios ORDER BY creado_en DESC LIMIT 10;

-- UPDATE - Actualizar
UPDATE usuarios
SET nombre = 'Juan Carlos'
WHERE id = 1;

-- DELETE - Eliminar
DELETE FROM usuarios WHERE id = 5;

JOINs

joins.sql
SELECT
    p.titulo,
    p.creado_en,
    u.nombre AS "autor"
FROM posts AS p
INNER JOIN usuarios AS u
    ON p.usuario_id = u.id;

SELECT u.nombre, COUNT(p.id) AS "total_posts"
FROM usuarios u
LEFT JOIN posts p ON u.id = p.usuario_id
GROUP BY u.id, u.nombre
HAVING COUNT(p.id) > 0;

🔗 Conexión Frontend-Backend

Flujo de datos

Flujo típico:

1. Usuario interactúa con HTML/CSS
2. JavaScript captura el evento
3. JavaScript envía HTTP al servidor
4. PHP procesa y consulta MySQL
5. PHP responde con JSON
6. JavaScript actualiza el DOM

Ejemplo: Formulario de Contacto

formulario.js
const form = document.getElementById("contactForm");

form.addEventListener("submit", async (e) => {
    e.preventDefault();

    // Obtener datos del formulario
    const formData = new FormData(form);
    const data = Object.fromEntries(formData.entries());

    try {
        const response = await fetch("procesar.php", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(data)
        });

        const result = await response.json();

        // Mostrar resultado
        document.getElementById("mensaje").textContent = result.mensaje;

        if (result.exito) {
            form.reset();
        }
    } catch (error) {
        console.error(error);
    }
});
procesar.php
<?php
header("Content-Type: application/json; charset=UTF-8");

// Solo aceptar POST
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    http_response_code(405);
    echo json_encode(["exito" => false, "mensaje" => "Método no permitido"]);
    exit;
}

// Obtener datos JSON
$json = file_get_contents("php://input");
$data = json_decode($json, true);

// Validar
if (!isset($data["nombre"]) || !isset($data["email"])) {
    echo json_encode(["exito" => false, "mensaje" => "Datos incompletos"]);
    exit;
}

// Guardar en BD...
echo json_encode(["exito" => true, "mensaje" => "Mensaje enviado"]);
?>

🛡️ Seguridad Web

SQL Injection

❌ Código vulnerable - NUNCA hacer esto:

⚠️ Peligro
// ¡PELIGRO! Concatenación directa
$query = "SELECT * FROM users WHERE email = '" . $_POST["email"] . "'";

// Un atacante puede injectar: ' OR '1'='1
// Resultado: SELECT * FROM users WHERE email = '' OR '1'='1'

✅ Solución segura - Prepared Statements:

seguro.php
// Prepared statement seguro
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$_POST["email"]]);
$user = $stmt->fetch();

XSS (Cross-Site Scripting)

prevenir-xss.js
// ✅ Usar textContent en vez de innerHTML
elem.textContent = userInput;

// ✅ Función para escapar HTML
const escapeHTML = (str) => {
    return str
        .replace(/&/g, "&")
        .replace(/, "<")
        .replace(/>/g, ">")
        .replace(/"/g, """)
        .replace(/'/g, "'");
};

Passwords Seguros

passwords.php
<?php
// Hash password con bcrypt
$hash = password_hash($password, PASSWORD_DEFAULT);

// Verificar password
if (password_verify($password, $hash)) {
    // Password correcto
}

// Verificar y rehash si necesario
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
    $nuevoHash = password_hash($password, PASSWORD_DEFAULT);
    // Actualizar en BD
}
?>

Checklist de Seguridad:

  • Usar HTTPS en producción
  • Prepared statements para TODAS las queries SQL
  • Escapar outputs antes de mostrarlos
  • Hash bcrypt para passwords
  • Validar y sanitizar TODOS los inputs