Ir al contenido principal

Sistema de Templates en JavaScript: Separa Lógica de Presentación

Sistema de Templates en JavaScript: Separa Lógica de Presentación como un Profesional

¿Tu código JavaScript mezcla lógica de negocio con HTML? ¿Cambiar el diseño significa modificar múltiples funciones? Descubre el poder de los templates para crear código mantenible y flexible.

❌ El Problema: HTML Incrustado en Lógica

Antes tenía HTML mezclado con mi lógica JavaScript, creando un caos de mantenimiento:

// ❌ HTML incrustado en la lógica - difícil de mantener
async lista(page = 1, filtro = '') {
    const response = await fetch(`${this.server}/api/productos?page=${page}`);
    const data = await response.json();
    
    // HTML directamente en el método
    tbody.innerHTML = data.items.map(item => `
        <tr data-line_id="${item.producto}">
            <td>${item.producto}</td>
            <td>${item.descripcion}</td>
            <td>${item.unidadMedida}</td>
            <td>${new Intl.NumberFormat('es-PY').format(item.precioUnitario)}</td>
            <td>${item.afectacionIva}</td>
        </tr>
    `).join('');
    
    // Mismo HTML repetido en otro método
    tbody.innerHTML = data.items.map(item => `
        <tr data-line_id="${item.producto}">
            <td>${item.producto}</td>
            <td>${item.descripcion}</td>
            <td>${item.unidadMedida}</td>
            <td>${new Intl.NumberFormat('es-PY').format(item.precioUnitario)}</td>
            <td>${item.afectacionIva}</td>
        </tr>
    `).join('');
}
Problemas: Código duplicado, cambios tediosos, difícil reutilización y mezcla de responsabilidades.

✅ La Solución: Sistema de Templates Centralizado

Implementé un sistema donde cada clase define sus templates de forma organizada:

// ✅ ModeloBase.js - Maneja la lógica, usa templates
export default class ModeloBase {
    constructor(recurso) {
        this.recurso = recurso;
        this.template = null; // Template por defecto
    }

    setTemplate(template) {
        this.template = template;
    }

    async lista(page = 1, filtro = '') {
        const response = await fetch(`${this.server}/api/${this.recurso}?page=${page}`);
        const data = await response.json();
        
        // ✅ Lógica limpia - solo usa el template
        if (this.template) {
            tbody.innerHTML = data.items.map(item => this.template(item)).join('');
        }
    }
}
// ✅ Producto.js - Define SU template específico
import ModeloBase from './ModeloBase.js';

export class Producto extends ModeloBase {
    constructor() {
        super('productos');
        
        // ✅ Template centralizado y reutilizable
        this.setTemplate(item => `
            <tr data-line_id="${item.producto}">
                <td data-field="producto">${item.producto}</td>
                <td data-field="descripcion">${item.descripcion}</td>
                <td data-field="unidadMedida">${item.unidadMedida}</td>
                <td data-field="precioUnitario">
                    ${new Intl.NumberFormat('es-PY', { 
                        minimumFractionDigits: 0 
                    }).format(item.precioUnitario)}
                </td>
                <td data-field="afectacionIva">${item.afectacionIva}</td>
            </tr>
        `);
    }
}

🎯 Flexibilidad en Acción: Múltiples Templates

Puedes definir diferentes templates para diferentes contextos:

// ✅ Producto con múltiples vistas
export class Producto extends ModeloBase {
    constructor() {
        super('productos');
        
        // Template para vista de tabla
        this.tableViewTemplate = item => `
            <tr data-line_id="${item.producto}">
                <td>${item.producto}</td>
                <td>${item.descripcion}</td>
                <td>${this.formatearPrecio(item.precioUnitario)}</td>
            </tr>
        `;
        
        // Template para vista de cards
        this.cardViewTemplate = item => `
            <div class="card" data-line_id="${item.producto}">
                <h3>${item.descripcion}</h3>
                <p>Precio: ${this.formatearPrecio(item.precioUnitario)}</p>
                <button onclick="seleccionarProducto(${item.producto})">
                    Seleccionar
                </button>
            </div>
        `;
        
        // Template para vista móvil
        this.mobileViewTemplate = item => `
            <div class="mobile-item">
                <strong>${item.descripcion}</strong>
                <small>${this.formatearPrecio(item.precioUnitario)}</small>
            </div>
        `;
    }
    
    formatearPrecio(precio) {
        return new Intl.NumberFormat('es-PY', {
            minimumFractionDigits: 0
        }).format(precio);
    }
    
    cambiarVista(tipoVista) {
        switch(tipoVista) {
            case 'tabla':
                this.setTemplate(this.tableViewTemplate);
                break;
            case 'cards':
                this.setTemplate(this.cardViewTemplate);
                break;
            case 'mobile':
                this.setTemplate(this.mobileViewTemplate);
                break;
        }
        this.lista(1, ''); // Recargar con nuevo template
    }
}

📊 Comparación: Antes vs Ahora

Aspecto Antes (HTML en lógica) Ahora (Sistema de Templates)
Cambios de diseño Modificar múltiples métodos Cambiar un solo template
Reutilización Código duplicado Templates reutilizables
Mantenibilidad Frágil y propenso a errores Robusto y organizado
Responsabilidades Mezcladas Separadas claramente
Flexibilidad Rígido Múltiples vistas fácilmente

🚀 Casos de Uso Avanzados

Template con Lógica Condicional
// ✅ Template inteligente con condiciones
this.setTemplate(item => `
    <tr data-line_id="${item.producto}" class="${item.stock === 0 ? 'sin-stock' : ''}">
        <td>${item.producto}</td>
        <td>${item.descripcion}</td>
        <td>
            ${item.stock === 0 
                ? '<span class="agotado">Agotado</span>'
                : `<span class="disponible">${item.stock} unidades</span>`
            }
        </td>
        <td>${this.formatearPrecio(item.precioUnitario)}</td>
    </tr>
`);
Template con Componentes Reutilizables
// ✅ Templates que usan componentes
export class Producto extends ModeloBase {
    constructor() {
        super('productos');
        this.setTemplate(item => this.productoTemplate(item));
    }
    
    productoTemplate(item) {
        return `
            <tr data-line_id="${item.producto}">
                <td>${this.formatoCodigo(item.producto)}</td>
                <td>${this.formatoDescripcion(item.descripcion)}</td>
                <td>${this.formatoPrecio(item.precioUnitario)}</td>
                <td>${this.formatoStock(item.stock)}</td>
            </tr>
        `;
    }
    
    formatoPrecio(precio) {
        return `<span class="precio">${new Intl.NumberFormat('es-PY').format(precio)}</span>`;
    }
    
    formatoStock(stock) {
        const clase = stock === 0 ? 'agotado' : stock < 10 ? 'poco-stock' : 'disponible';
        return `<span class="${clase}">${stock} unidades</span>`;
    }
}

📈 Beneficios del Sistema de Templates

✅ Separación de Concerns Lógica y presentación claramente separadas
✅ Mantenibilidad Cambios de diseño en un solo lugar
✅ Reutilización Templates compartidos entre componentes
✅ Testing Puedes testear templates independientemente
✅ Flexibilidad Múltiples vistas sin cambiar lógica
✅ Colaboración Designers y developers trabajan separadamente
Conclusión
Los templates no son solo para frameworks modernos. En JavaScript vanilla, un sistema de templates bien diseñado puede transformar tu código de caótico a profesional, permitiéndote escalar tu aplicación manteniendo el código limpio y mantenible.

Comentarios

Entradas populares de este blog

Instalación y Configuración de MySQL 5.7 en Ubuntu 24.04 LTS

Instalar MySQL 5.7 en Ubuntu 24.04 1. Descargar e instalar MySQL Copiar mkdir ~/mysql57 cd ~/mysql57 wget https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz tar -zxvf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz sudo mv mysql-5.7.44-linux-glibc2.12-x86_64 /usr/local/mysql sudo ln -s /usr/local/mysql/bin/mysql /usr/local/bin/mysql 2. Instalar dependencias necesarias IMPORTANTE: Se descargan las versiones nuevas de las librerías y se las vincula con las librerías que necesita MySQL. Copiar sudo apt update # Reemplazo de libaio sudo apt install libaio1t64 # Reemplazo de libtinfo y ncurses sudo apt install libtinfo6 libncurses6 Copiar # Crear los enlaces simbólicos sudo ln -sf /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/libaio.so.1 sudo ln -sf /usr/lib/x86_64-linux-gnu/libtinfo.so.6 /usr/lib/x86_64-linux-gnu/libtinfo.so.5 sudo ln -sf /usr/lib/x86_64-linux-gnu/libncurses.so.6 /usr/lib/x86_64...

Instalar Evolution API en Docker con Redis y PostgreSQL Local

Instalar Evolution API en Docker con Redis y PostgreSQL Local En este tutorial vamos a levantar Evolution API usando Docker , con soporte de Redis para sesiones y PostgreSQL local para almacenar datos de manera persistente y compartida entre varios usuarios. 1. Estructura del proyecto Crea una carpeta para tu proyecto y colócate en ella: mkdir -p ~/docker/evolution-api cd ~/docker/evolution-api 2. Archivo docker-compose.yml Este compose levanta Redis y Evolution API : version: "3.9" services: # ✅ SERVICIO REDIS redis: container_name: evolution_redis image: redis:7-alpine restart: unless-stopped ports: - "6379:6379" volumes: - redis_data:/data command: redis-server --save 60 1 --loglevel warning # ✅ SERVICIO EVOLUTION API evolution-api: container_name: evolution_api image: atendai/evolution-api restart: unless-stopped ports: - "8085:8080" env_file: - .env ...

Instalar Jasper Studio 6.21 para Ubuntu 24.04

Instalar js-studiocomm_6.21.3 en Ubuntu 24.4 Para instalar Jaspersoft Studio en Ubuntu 24.4, sigue estos pasos: 1. Descargar Jasper Studio Descarga la versión js-studiocomm_6.21.3 desde el siguiente enlace: Jaspersoft Studio 6.21.3 2. Crear el directorio de instalación mkdir /opt/jasperstudio 3. Mover el archivo descargado mv /dir_descarga/js-studiocomm_6.21.3_linux_x86_64.tgz /opt/jasperstudio/ cd /opt/jasperstudio 4. Extraer el archivo tar -xvzf js-studiocomm_6.21.3_linux_x86_64.tgz cd js-studiocomm_6.21.3 5. Ejecutar Jaspersoft Studio ./Jaspersoft\ Studio 6. Crear acceso directo en el escritorio Para facilitar el acceso, crea un archivo .desktop en el escritorio: gedit ~/Escritorio/jaspersoft-studio.desktop En el archivo jaspersoft-studio.desktop , agrega lo siguiente: [Desktop Entry] Version=1.0 Ty...