Tema: Modelo Vista Controlador (MVC)

En esta clase vamos a entender qué es MVC, qué problema resuelve, qué son los controladores, qué son los modelos, qué son las vistas, qué es un endpoint y cómo todo esto se relaciona con lo que ya hicimos en PHP + MySQL.

1) Qué problema resuelve MVC

Hasta ahora hemos trabajado de forma muy directa: un archivo PHP puede tener HTML, consultas SQL, validaciones, formularios y redirecciones en el mismo lugar. Eso funciona para aprender, pero conforme el proyecto crece se vuelve difícil de mantener.

Por ejemplo, un archivo como venta_save_post.php puede recibir POST, validar datos, consultar productos, calcular total, insertar una venta y mostrar una respuesta. Todo eso en un solo archivo.

Problema:
Cuando mezclamos todo en un mismo archivo, después es difícil saber dónde está cada cosa: la consulta, el formulario, la validación, la respuesta, el HTML, el JS, etc.

MVC propone separar responsabilidades:

2) Analogía de vida real: restaurante

Imagina un restaurante:

En MVC sería:

El cliente no entra a la cocina. El usuario no debería hablar directo con la base de datos. El controlador coordina y el modelo trabaja con datos.

3) Analogía con videojuegos

En un videojuego, cuando presionas un botón para atacar:

En MVC:

4) Qué es una Vista

La vista es lo que el usuario ve: HTML, tablas, formularios, botones, mensajes.

En tu proyecto actual, ejemplos de vistas serían:

Idea clave:
Una vista debería enfocarse principalmente en mostrar información, no en hacer toda la lógica pesada.

5) Qué es un Modelo

El modelo representa la parte que trabaja con datos. En una aplicación PHP + MySQL, normalmente el modelo hace consultas SQL.

En nuestro caso, el modelo puede usar las clases que ya conocemos: Conexion y Functions.

Ejemplo conceptual:

// models/UsuarioModel.php

class UsuarioModel
{
  public function listar()
  {
    $functions = new Functions();

    return $functions->execute("
      SELECT id, nombre, correo, rol, activo, created_at
      FROM usuarios
      ORDER BY id DESC
    ");
  }
}
Qué hace este modelo:
No imprime HTML. No muestra botones. No sabe cómo se verá la pantalla. Solo sabe consultar usuarios y devolver el resultado.

6) Qué es un Controlador

El controlador recibe una petición y decide qué hacer.

Puede recibir:

En tu proyecto ya vimos algo parecido con:

/controllers/AuthController.php?action=login

Ese controlador recibe datos, valida usuario y contraseña, crea sesión y redirige.

Idea clave:
El controlador no debería ser “una página bonita”. Su trabajo es coordinar: recibe petición, llama al modelo, decide respuesta.

7) Qué es un Endpoint

Un endpoint es una URL específica de tu sistema que recibe una petición y devuelve una respuesta.

Ejemplos:

/controllers/UsuarioController.php?action=listar
/controllers/UsuarioController.php?action=guardar
/controllers/VentaController.php?action=guardar
/controllers/AuthController.php?action=login

Cada endpoint debería tener una responsabilidad clara.

Endpoint no significa necesariamente API complicada.
En nuestro nivel, un endpoint puede ser simplemente un archivo PHP con una acción: UsuarioController.php?action=listar.

8) Antes vs Después

Antes teníamos archivos sueltos:

pages/usuarios_list.php
pages/venta_form_post.php
pages/venta_save_post.php
pages/venta_save_async.php

En MVC podríamos empezar a organizar así:

/models
  UsuarioModel.php
  VentaModel.php

/controllers
  UsuarioController.php
  VentaController.php

/views
  /usuarios
    list.php
    form.php
  /ventas
    list.php
    form.php
Importante:
MVC no es crear carpetas por crear carpetas. MVC es separar responsabilidades. Si metes todo mezclado dentro del controlador, aunque tengas carpetas, no estás aplicando bien la idea.

9) Flujo de formulario POST con controlador

Antes hacíamos esto:

<form method="post" action="venta_save_post.php">

Con controlador sería:

<form method="post" action="/controllers/VentaController.php?action=guardar">

Flujo:

  1. La vista muestra el formulario.
  2. El usuario llena datos y da submit.
  3. El formulario manda POST al controlador.
  4. El controlador valida datos.
  5. El controlador llama al modelo.
  6. El modelo guarda en la base de datos.
  7. El controlador redirige o responde JSON.

10) Ejemplo práctico: Modelo de Usuario

Este ejemplo no lo vamos a implementar todavía en el proyecto completo. Sirve para entender cómo se empezaría a separar la lógica.

Ejemplo models/UsuarioModel.php
<?php
// models/UsuarioModel.php

require_once __DIR__ . '/../lib/Conexion.php';
require_once __DIR__ . '/../lib/Functions.php';

class UsuarioModel
{
  private $functions;

  public function __construct()
  {
    // Reutilizamos la clase Functions que ya conocemos.
    // Functions contiene execute($sql), que ejecuta consultas SQL.
    $this->functions = new Functions();
  }

  public function listar()
  {
    // El modelo se encarga de hablar con la base de datos.
    // No imprime HTML, solo devuelve el resultado.
    return $this->functions->execute("
      SELECT id, nombre, correo, rol, activo, created_at
      FROM usuarios
      ORDER BY id DESC
    ");
  }

  public function buscarPorId($id)
  {
    $id = (int)$id;

    return $this->functions->execute("
      SELECT id, nombre, correo, rol, activo, created_at
      FROM usuarios
      WHERE id = $id
      LIMIT 1
    ");
  }
}

11) Ejemplo práctico: Controlador de Usuario

El controlador recibe acciones por URL. Por ejemplo: /controllers/UsuarioController.php?action=listar

Ejemplo controllers/UsuarioController.php
<?php
// controllers/UsuarioController.php

require_once __DIR__ . '/../models/UsuarioModel.php';

$action = $_GET['action'] ?? '';

$model = new UsuarioModel();

if ($action === 'listar') {
  // 1) El controlador pide los datos al modelo
  $getUsers = $model->listar();

  // 2) Valida si hubo error
  if ($getUsers['res'] !== 'ok') {
    die($getUsers['res']);
  }

  // 3) El controlador carga una vista
  // La vista usará $getUsers para pintar la tabla.
  require_once __DIR__ . '/../views/usuarios/list.php';
  exit;
}

if ($action === 'ver') {
  $id = $_GET['id'] ?? 0;

  $getUser = $model->buscarPorId($id);

  if ($getUser['res'] !== 'ok') {
    die($getUser['res']);
  }

  require_once __DIR__ . '/../views/usuarios/detail.php';
  exit;
}

// Si la acción no existe
echo "Acción no válida.";

12) Ejemplo práctico: Vista de Usuarios

La vista recibe datos ya preparados por el controlador. Aquí sí se encarga de pintar HTML.

Ejemplo views/usuarios/list.php
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>Usuarios</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>

<body class="bg-light">
<div class="container py-5">

  <h1>Usuarios</h1>
  <p class="text-muted">Vista renderizada desde MVC.</p>

  <div class="card shadow-sm">
    <div class="card-body">
      <table class="table table-striped">
        <thead>
          <tr>
            <th>ID</th>
            <th>Nombre</th>
            <th>Correo</th>
            <th>Rol</th>
            <th>Activo</th>
          </tr>
        </thead>
        <tbody>

          <?php while ($row = $getUsers['query']->fetch_assoc()) { ?>
            <?php foreach ($row as $key => $value) { $$key = $value; } ?>

            <tr>
              <td><?php echo $id; ?></td>
              <td><?php echo $nombre; ?></td>
              <td><?php echo $correo; ?></td>
              <td><?php echo $rol; ?></td>
              <td>
                <?php if ($activo == 1) { ?>
                  <span class="badge text-bg-success">Activo</span>
                <?php } else { ?>
                  <span class="badge text-bg-danger">Inactivo</span>
                <?php } ?>
              </td>
            </tr>
          <?php } ?>

        </tbody>
      </table>
    </div>
  </div>

</div>
</body>
</html>

13) Endpoint que responde JSON

Un controlador no siempre carga una vista. También puede responder JSON para que JavaScript lo consuma.

Ejemplo controllers/UsuarioApiController.php?action=listar
<?php
// controllers/UsuarioApiController.php

header('Content-Type: application/json; charset=utf-8');

require_once __DIR__ . '/../models/UsuarioModel.php';

$action = $_GET['action'] ?? '';

$model = new UsuarioModel();

if ($action === 'listar') {
  $getUsers = $model->listar();

  if ($getUsers['res'] !== 'ok') {
    echo json_encode([
      "ok" => false,
      "msg" => $getUsers['res']
    ]);
    exit;
  }

  $usuarios = [];

  while ($row = $getUsers['query']->fetch_assoc()) {
    $usuarios[] = $row;
  }

  echo json_encode([
    "ok" => true,
    "data" => $usuarios
  ]);
  exit;
}

echo json_encode([
  "ok" => false,
  "msg" => "Acción no válida"
]);

14) Renderizar HTML desde JavaScript

Si un endpoint devuelve JSON, JavaScript puede leer esos datos y crear HTML dinámicamente.

Esto ya no es MVC clásico puro con PHP renderizando vistas. Esto se parece más a:

Frontend JS → Endpoint PHP → Modelo → BD → JSON → JS pinta HTML
Ejemplo assets/js/usuarios_api.js
// assets/js/usuarios_api.js

document.addEventListener("DOMContentLoaded", async () => {
  const tbody = document.getElementById("tbodyUsuarios");

  if (!tbody) return;

  try {
    const res = await fetch("/controllers/UsuarioApiController.php?action=listar");
    const json = await res.json();

    if (!json.ok) {
      tbody.innerHTML = `<tr><td colspan="5">${json.msg}</td></tr>`;
      return;
    }

    tbody.innerHTML = "";

    json.data.forEach(usuario => {
      tbody.innerHTML += `
        <tr>
          <td>${usuario.id}</td>
          <td>${usuario.nombre}</td>
          <td>${usuario.correo}</td>
          <td>${usuario.rol}</td>
          <td>${usuario.activo == 1 ? "Activo" : "Inactivo"}</td>
        </tr>
      `;
    });

  } catch (error) {
    tbody.innerHTML = `<tr><td colspan="5">Error al cargar usuarios.</td></tr>`;
  }
});
Idea importante:
Si PHP devuelve HTML, la vista está del lado servidor. Si PHP devuelve JSON y JS pinta HTML, la vista se arma en el navegador.

15) Resumen final

MVC no es un framework. MVC es una forma de ordenar responsabilidades. Laravel, CodeIgniter, Symfony y otros frameworks usan ideas de MVC, pero el concepto se puede entender desde PHP simple.

16) Ejercicios para pizarrón

  1. Explica con tus palabras qué problema resuelve MVC.
  2. Di qué parte sería modelo, vista y controlador en un formulario de ventas.
  3. Escribe un endpoint inventado para listar productos.
  4. Explica la diferencia entre responder con HTML y responder con JSON.
  5. Explica qué archivo debería hablar con la base de datos: ¿vista, modelo o controlador?