Versión MVC con post

 Versión MVC SIN routes (todo en app.js)

Aquí tienes el ejercicio refactorizado a MVC sin carpeta de rutas:

📁 ESTRUCTURA DEL PROYECTO

text
mi-app-mvc/
├── models/
│   └── Producto.js
├── controllers/
│   └── productoController.js
├── views/
│   ├── index.ejs
│   └── productos.ejs
├── public/
│   └── (archivos estáticos)
└── app.js

📄 CÓDIGO COMPLETO

1️⃣ models/Producto.js (Modelo - Datos)

javascript
// Modelo: Maneja toda la lógica de datos
class Producto {
    constructor() {
        this.productos = [
            { id: 1, nombre: 'Laptop', precio: 1000 },
            { id: 2, nombre: 'Mouse', precio: 25 },
            { id: 3, nombre: 'Teclado', precio: 50 }
        ];
        this.contadorId = 4;
    }

    // Obtener todos los productos
    obtenerTodos() {
        return this.productos;
    }

    // Obtener producto por ID
    obtenerPorId(id) {
        return this.productos.find(p => p.id === id);
    }

    // Crear nuevo producto
    crear(nombre, precio) {
        const nuevoProducto = {
            id: this.contadorId++,
            nombre: nombre.trim(),
            precio: parseFloat(precio)
        };
        this.productos.push(nuevoProducto);
        return nuevoProducto;
    }

    // Obtener cantidad total
    obtenerTotal() {
        return this.productos.length;
    }
}

module.exports = new Producto();

2️⃣ controllers/productoController.js (Controlador - Lógica)

javascript
const Producto = require('../models/Producto');

class ProductoController {
    
    // GET / - Renderizar vista principal (index)
    mostrarInicio(req, res) {
        const productos = Producto.obtenerTodos();
        res.render('index', {
            title: 'Mi Tienda',
            productos: productos
        });
    }

    // GET /productos-vista - Renderizar vista de productos
    mostrarProductosVista(req, res) {
        const productos = Producto.obtenerTodos();
        const total = Producto.obtenerTotal();
        
        res.render('productos', {
            title: 'Lista de Productos',
            productos: productos,
            total: total
        });
    }

    // GET /productos - API: Devolver todos (JSON)
    obtenerTodosAPI(req, res) {
        const productos = Producto.obtenerTodos();
        res.json(productos);
    }

    // GET /productos/:id - API: Devolver uno (JSON)
    obtenerUnoAPI(req, res) {
        const id = parseInt(req.params.id);
        const producto = Producto.obtenerPorId(id);
        
        if (!producto) {
            return res.status(404).json({ 
                error: 'Producto no encontrado' 
            });
        }
        
        res.json(producto);
    }

    // POST /productos-formulario - Procesar formulario
    procesarFormulario(req, res) {
        const { nombre, precio } = req.body;
        
        // Validaciones
        if (!nombre || !precio) {
            return res.status(400).send(`
                <h1>Error</h1>
                <p>Faltan datos: nombre y precio son obligatorios</p>
                <a href="/">Volver al inicio</a>
            `);
        }
        
        if (nombre.trim().length < 3) {
            return res.status(400).send(`
                <h1>Error</h1>
                <p>El nombre debe tener al menos 3 caracteres</p>
                <a href="/">Volver al inicio</a>
            `);
        }
        
        if (isNaN(precio) || parseFloat(precio) <= 0) {
            return res.status(400).send(`
                <h1>Error</h1>
                <p>El precio debe ser un número mayor a 0</p>
                <a href="/">Volver al inicio</a>
            `);
        }
        
        // Crear producto
        const nuevoProducto = Producto.crear(nombre, precio);
        
        // Redirigir a la página principal
        res.redirect('/');
    }
}

module.exports = new ProductoController();

3️⃣ app.js (Configuración principal SIN router)

javascript
const express = require('express');
const path = require('path');
const productoController = require('./controllers/productoController');

const app = express();
const PORT = 3000;

// ============ CONFIGURACIÓN EJS ============
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// ============ MIDDLEWARES ============
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/css', express.static('node_modules/bootstrap/dist/css'));
app.use('/js', express.static('node_modules/bootstrap/dist/js'));

// ============ RUTAS (usando el controlador directamente) ============

// Rutas Web (EJS)
app.get('/', productoController.mostrarInicio);
app.get('/productos-vista', productoController.mostrarProductosVista);

// Rutas API (JSON)
app.get('/productos', productoController.obtenerTodosAPI);
app.get('/productos/:id', productoController.obtenerUnoAPI);

// Ruta para formulario
app.post('/productos-formulario', productoController.procesarFormulario);

// ============ INICIAR SERVIDOR ============
app.listen(PORT, () => {
    console.log(`=================================`);
    console.log(`🚀 Servidor MVC corriendo en http://localhost:${PORT}`);
    console.log(`=================================`);
    console.log(`📌 Vistas EJS:`);
    console.log(`   🏠 Inicio: http://localhost:${PORT}/`);
    console.log(`   📦 Productos: http://localhost:${PORT}/productos-vista`);
    console.log(``);
    console.log(`📌 API JSON:`);
    console.log(`   GET /productos`);
    console.log(`   GET /productos/1`);
    console.log(``);
    console.log(`📌 Formulario (POST):`);
    console.log(`   POST /productos-formulario`);
    console.log(`=================================`);
});

4️⃣ views/index.ejs (Vista principal)

html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= title %></title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
    <div class="container mt-5">
        <h1 class="text-center">📦 <%= title %></h1>
        
        <div class="row mt-4">
            <div class="col-md-8 offset-md-2">
                <div class="card">
                    <div class="card-header bg-primary text-white">
                        <h4 class="mb-0">Bienvenido a tu Tienda</h4>
                    </div>
                    <div class="card-body">
                        <p>Gestiona tus productos fácilmente</p>
                        
                        <div class="row">
                            <div class="col-md-6">
                                <div class="card text-center">
                                    <div class="card-body">
                                        <h5 class="card-title">📋 Ver Productos</h5>
                                        <p class="card-text">Consulta todos los productos disponibles</p>
                                        <a href="/productos-vista" class="btn btn-primary">Ver Productos</a>
                                    </div>
                                </div>
                            </div>
                            
                            <div class="col-md-6">
                                <div class="card text-center">
                                    <div class="card-body">
                                        <h5 class="card-title">➕ API REST</h5>
                                        <p class="card-text">Accede a los datos via JSON</p>
                                        <a href="/productos" class="btn btn-success">Ver API</a>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <!-- Formulario para agregar productos -->
        <div class="row mt-4">
            <div class="col-md-8 offset-md-2">
                <div class="card">
                    <div class="card-header bg-success text-white">
                        <h4 class="mb-0">➕ Agregar Nuevo Producto</h4>
                    </div>
                    <div class="card-body">
                        <form action="/productos-formulario" method="POST">
                            <div class="mb-3">
                                <label for="nombre" class="form-label">Nombre del producto</label>
                                <input type="text" class="form-control" id="nombre" name="nombre" required>
                            </div>
                            
                            <div class="mb-3">
                                <label for="precio" class="form-label">Precio</label>
                                <input type="number" class="form-control" id="precio" name="precio" step="0.01" required>
                            </div>
                            
                            <button type="submit" class="btn btn-success">💾 Guardar Producto</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        
        <!-- Info de arquitectura -->
        <div class="row mt-4">
            <div class="col-md-8 offset-md-2">
                <div class="alert alert-info">
                    <strong>📐 Arquitectura MVC</strong><br>
                    ✅ Modelo: Producto.js (datos)<br>
                    ✅ Vista: index.ejs y productos.ejs (interfaz)<br>
                    ✅ Controlador: productoController.js (lógica)<br>
                    ✅ Sin carpeta routes (todo en app.js)
                </div>
            </div>
        </div>
    </div>

    <script src="/js/bootstrap.min.js"></script>
</body>
</html>

5️⃣ views/productos.ejs (Lista de productos)

html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= title %></title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
    <div class="container mt-5">
        <h1 class="text-center">📦 <%= title %></h1>
        
        <div class="row mt-4">
            <div class="col-md-8 offset-md-2">
                <div class="card">
                    <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
                        <h4 class="mb-0">Productos disponibles</h4>
                        <span class="badge bg-light text-dark">Total: <%= total %></span>
                    </div>
                    <div class="card-body">
                        <% if (productos.length === 0) { %>
                            <div class="alert alert-warning">
                                No hay productos disponibles
                            </div>
                        <% } else { %>
                            <div class="table-responsive">
                                <table class="table table-striped">
                                    <thead>
                                        <tr>
                                            <th>ID</th>
                                            <th>Nombre</th>
                                            <th>Precio</th>
                                            <th>Acciones</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <% productos.forEach(producto => { %>
                                            <tr>
                                                <td><%= producto.id %></td>
                                                <td><%= producto.nombre %></td>
                                                <td>$<%= producto.precio %></td>
                                                <td>
                                                    <a href="/productos/<%= producto.id %>" class="btn btn-sm btn-info">Ver JSON</a>
                                                </td>
                                            </tr>
                                        <% }) %>
                                    </tbody>
                                </table>
                            </div>
                        <% } %>
                    </div>
                </div>
                
                <div class="text-center mt-3">
                    <a href="/" class="btn btn-secondary">← Volver al inicio</a>
                    <a href="/productos" class="btn btn-success">Ver API JSON</a>
                </div>
            </div>
        </div>
    </div>

    <script src="/js/bootstrap.min.js"></script>
</body>
</html>

🚀 CÓMO EJECUTAR

bash
# 1. Instalar dependencias
npm install express ejs bootstrap

# 2. Crear estructura de carpetas
mkdir models controllers views public

# 3. Crear los archivos con el código anterior

# 4. Ejecutar
node app.js

🎯 ENDPOINTS DISPONIBLES

MétodoURLDescripciónTipo
GET/Página principalWeb
GET/productos-vistaLista de productosWeb
GET/productosTodos los productosAPI JSON
GET/productos/1Un producto específicoAPI JSON
POST/productos-formularioCrear productoFormulario

📊 COMPARATIVA: CON routes vs SIN routes

CaracterísticaCON routesSIN routes
Archivos4 (models, controllers, views, routes)3 (models, controllers, views)
ComplejidadMediaBaja
Ideal paraProyectos medianos/grandesProyectos pequeños
MantenimientoMás organizadoMás directo

¡Listo! Ahora tienes MVC completo SIN carpeta de rutas

Comentarios

Entradas más populares de este blog

Cómo Iniciar un Proyecto Node.js

8-Template Engines

6-Middleware?