Tabla de contenido

La mejor forma de consolidar lo aprendido sobre PHP y MySQL es construir algo real. En este tutorial vamos a crear una lista de tareas (to-do list) funcional desde cero, aplicando las cuatro operaciones básicas: Crear, Leer, Actualizar y Eliminar (CRUD).

¿Qué vamos a construir?

Una aplicación web sencilla donde puedas:

  • Ver todas tus tareas pendientes
  • Agregar nuevas tareas
  • Marcar tareas como completadas
  • Eliminar tareas

No usaremos frameworks, solo PHP puro, MySQL y HTML básico, para que entiendas exactamente qué hace cada línea.

Configuración del entorno

Necesitas:

  • XAMPP (Apache + MySQL + PHP) — descarga desde apachefriends.org
  • Un editor de código (recomiendo VS Code)

Una vez instalado XAMPP:

  1. Inicia Apache y MySQL desde el panel de control
  2. Crea una carpeta lista-tareas dentro de htdocs
  3. Abre en el navegador http://localhost/lista-tareas

Crear la base de datos

Abre phpMyAdmin en http://localhost/phpmyadmin y ejecuta:

CREATE DATABASE lista_tareas;

USE lista_tareas;

CREATE TABLE tareas (
  id INT AUTO_INCREMENT PRIMARY KEY,
  titulo VARCHAR(200) NOT NULL,
  completada TINYINT(1) DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insertar algunas tareas de ejemplo
INSERT INTO tareas (titulo) VALUES
  ('Aprender PHP'),
  ('Practicar MySQL'),
  ('Crear un CRUD completo');

Conexión con PDO

Crea el archivo db.php:

<?php
$host = 'localhost';
$db   = 'lista_tareas';
$user = 'root';
$pass = '';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    die("Error de conexión: " . $e->getMessage());
}
?>

Listar tareas (Read)

Crea index.php:

<?php
require 'db.php';

// Obtener todas las tareas ordenadas por fecha
$stmt = $pdo->query("SELECT * FROM tareas ORDER BY created_at DESC");
$tareas = $stmt->fetchAll();
?>

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Lista de Tareas</title>
  <style>
    body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
    .tarea { display: flex; align-items: center; gap: 10px; padding: 10px; border-bottom: 1px solid #eee; }
    .completada span { text-decoration: line-through; color: #999; }
    input[type="text"] { flex: 1; padding: 8px; font-size: 1rem; border: 1px solid #ccc; border-radius: 4px; }
    button { padding: 8px 16px; cursor: pointer; border: none; border-radius: 4px; }
    .btn-agregar { background: #4CAF50; color: white; }
    .btn-completar { background: #2196F3; color: white; font-size: 12px; }
    .btn-eliminar { background: #f44336; color: white; font-size: 12px; }
  </style>
</head>
<body>
  <h1>Mi Lista de Tareas</h1>

  <!-- Formulario para agregar tarea -->
  <form method="POST" action="crear.php" style="display:flex; gap:10px; margin-bottom:20px;">
    <input type="text" name="titulo" placeholder="Nueva tarea..." required>
    <button type="submit" class="btn-agregar">Agregar</button>
  </form>

  <!-- Lista de tareas -->
  <?php if (empty($tareas)): ?>
    <p>No tienes tareas pendientes. ¡Añade una!</p>
  <?php else: ?>
    <?php foreach ($tareas as $tarea): ?>
      <div class="tarea <?= $tarea['completada'] ? 'completada' : '' ?>">
        <span><?= htmlspecialchars($tarea['titulo']) ?></span>
        <div style="margin-left:auto; display:flex; gap:5px;">
          <?php if (!$tarea['completada']): ?>
            <form method="POST" action="completar.php">
              <input type="hidden" name="id" value="<?= $tarea['id'] ?>">
              <button type="submit" class="btn-completar"> Completar</button>
            </form>
          <?php endif; ?>
          <form method="POST" action="eliminar.php">
            <input type="hidden" name="id" value="<?= $tarea['id'] ?>">
            <button type="submit" class="btn-eliminar"> Eliminar</button>
          </form>
        </div>
      </div>
    <?php endforeach; ?>
  <?php endif; ?>
</body>
</html>

Crear una tarea (Create)

Crea crear.php:

<?php
require 'db.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['titulo'])) {
    $titulo = trim($_POST['titulo']);
    
    $stmt = $pdo->prepare("INSERT INTO tareas (titulo) VALUES (:titulo)");
    $stmt->execute([':titulo' => $titulo]);
}

header("Location: index.php");
exit;
?>

Completar una tarea (Update)

Crea completar.php:

<?php
require 'db.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['id'])) {
    $id = (int) $_POST['id'];
    
    $stmt = $pdo->prepare("UPDATE tareas SET completada = 1 WHERE id = :id");
    $stmt->execute([':id' => $id]);
}

header("Location: index.php");
exit;
?>

Eliminar una tarea (Delete)

Crea eliminar.php:

<?php
require 'db.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['id'])) {
    $id = (int) $_POST['id'];
    
    $stmt = $pdo->prepare("DELETE FROM tareas WHERE id = :id");
    $stmt->execute([':id' => $id]);
}

header("Location: index.php");
exit;
?>

Con estos 5 archivos (db.php, index.php, crear.php, completar.php, eliminar.php) tienes una aplicación CRUD completamente funcional. Presta atención a estos puntos de seguridad:

  • Usamos sentencias preparadas con prepare() y execute() para prevenir SQL Injection
  • Usamos htmlspecialchars() al mostrar datos del usuario para prevenir XSS
  • Convertimos el ID a entero con (int) antes de usarlo en queries

Esta es la base de cualquier aplicación web con PHP. A partir de aquí puedes agregar autenticación, validaciones más robustas, CSS con Bootstrap, o migrar a un framework como Laravel.