Tema 5 · Privacidad & ES2022

Encapsulamiento Moderno: Campos Privados (#), Getters y Setters

Aprende a proteger el estado interno de tus clases mediante la sintaxis nativa de campos privados (#) de ES2022 y controla el acceso con getters y setters.

La Larga Búsqueda de la Privacidad en JavaScript

Durante más de 20 años, JavaScript no tuvo ningún mecanismo oficial para declarar propiedades privadas como private en PHP o Java.

Los programadores recurrían a convenciones como anteponer un guión bajo (this._saldo = 100;), pidiéndole amablemente a otros desarrolladores que "por favor no lo modificaran". Evidentemente, esto era un falso encapsulamiento: cualquiera podía alterar el valor por error y romper la aplicación.

La Solución Definitiva: Campos Privados con `#` (ES2022)

A partir de la especificación ECMAScript 2022, JavaScript incorporó campos y métodos verdaderamente privados utilizando el prefijo de almohadilla #.

Blindaje Nativo por el Motor V8: Un campo declarado con # es completamente inaccesible desde el exterior. Si intentas escribir objeto.#saldo fuera de la clase, el motor de JavaScript lanzará un SyntaxError fatal impidiendo cualquier intento de lectura o modificación.

Diagrama: La Frontera de Seguridad en Clases JS

Diagrama Campos Privados Hash en JavaScript

Figura 5.1: Separación estricta entre la zona pública accesible y la zona interna con hash (#).

Getters y Setters Modernos

Para exponer de forma controlada o formateada el valor de un campo privado, utilizamos las palabras clave get y set:

class Usuario {
    #nombre;

    get nombre() {
        return this.#nombre.toUpperCase(); // Acceso tipo propiedad: u.nombre
    }

    set nombre(valor) {
        if (!valor || valor.trim().length === 0) throw new Error("Nombre inválido");
        this.#nombre = valor.trim();
    }
}

⚡ Billetera Digital con Campos Privados (#), PIN y Validaciones

Ejecuta el código en el Sandbox y observa cómo los campos con # bloquean accesos indebidos:

BilleteraPrivada.js
Solo lectura
class BilleteraDigital {
    // 1. Declaración obligatoria de Campos Privados (#)
    #saldo = 0;
    #pin;
    #historialTransacciones = [];

    constructor(titular, saldoInicial, pin) {
        this.titular = titular; // Propiedad pública
        this.#saldo = Math.max(0, saldoInicial);
        this.#pin = String(pin);
        this.#registrarEvento(`Cuenta creada con saldo inicial de $${this.#saldo}`);
    }

    // 2. Método Privado (Solo utilizable dentro de la clase)
    #registrarEvento(descripcion) {
        this.#historialTransacciones.push({
            fecha: new Date().toLocaleTimeString(),
            detalle: descripcion
        });
    }

    #verificarPin(pinIngresado) {
        return String(pinIngresado) === this.#pin;
    }

    // 3. Getter Público: Permite leer el saldo sin permitir alterarlo directamente
    get saldo() {
        return `$${this.#saldo.toFixed(2)}`;
    }

    // 4. Métodos Públicos Seguros
    depositar(monto) {
        if (monto <= 0) {
            console.warn("⚠️ El monto a depositar debe ser mayor a 0.");
            return;
        }
        this.#saldo += monto;
        this.#registrarEvento(`Depósito de +$${monto.toFixed(2)}`);
        console.log(` Depósito exitoso. Nuevo saldo disponible: ${this.saldo}`);
    }

    transferir(monto, cuentaDestino, pinIngresado) {
        if (!this.#verificarPin(pinIngresado)) {
            console.error("⛔ PIN de seguridad incorrecto. Operación bloqueada.");
            return false;
        }

        if (monto > this.#saldo) {
            console.error(`❌ Fondos insuficientes. Intentas transferir $${monto} pero tienes ${this.saldo}`);
            return false;
        }

        this.#saldo -= monto;
        this.#registrarEvento(`Transferencia a [${cuentaDestino}] de -$${monto.toFixed(2)}`);
        console.log(` Transferencia de $${monto.toFixed(2)} a ${cuentaDestino} completada con éxito.`);
        return true;
    }

    verHistorial(pinIngresado) {
        if (!this.#verificarPin(pinIngresado)) {
            console.error("⛔ Acceso denegado al historial.");
            return;
        }
        console.log(`\n=== HISTORIAL DE ${this.titular.toUpperCase()} ===`);
        this.#historialTransacciones.forEach(t => console.log(`• [${t.fecha}] ${t.detalle}`));
    }
}

// ==========================================
// PRUEBA EN EL SANDBOX
// ==========================================
const miBilletera = new BilleteraDigital("Valeria Torres", 500, "1234");

console.log(`Titular público: ${miBilletera.titular}`);
console.log(`Saldo inicial (vía getter): ${miBilletera.saldo}`);

console.log("\n--- OPERACIONES CONTROLADAS ---");
miBilletera.depositar(200);
miBilletera.transferir(150, "Carlos Mendoza", "9999"); // Error de PIN
miBilletera.transferir(150, "Carlos Mendoza", "1234"); // PIN Correcto

miBilletera.verHistorial("1234");

// DEMOSTRACIÓN DE BLINDAJE PRIVADO:
// Intento de hackear el saldo desde fuera:
// miBilletera.#saldo = 999999; // ❌ Descomentar esto causa: SyntaxError: Private field '#saldo' must be declared in an enclosing class
Pruébalo tú mismo » Se abre en una nueva pestaña con ejecución en vivo
🧠
Mini-Quiz de Retención
Comprueba tu comprensión antes de pasar a la siguiente lección
1 Pregunta
Pregunta 1
¿Cuál es el símbolo oficial introducido en el estándar de JavaScript moderno (ES2022) para declarar un campo o método como estrictamente privado dentro de una clase?
🦉
¿Tienes dudas con este tema?
Sócrates AI puede darte analogías y explicarte este código paso a paso.

🚀 ¿Quieres dominar Manual de Programación Orientada a Objetos en JavaScript (POO) y crear proyectos reales?

Aprende arquitectura limpia, desarrollo web moderno, buenas prácticas y despliegue en producción con nuestro Bootcamp Fullstack Developer con mentorías en vivo.

🏛️ Autoría y Propiedad Intelectual: Obra educativa original de David Amador para Kilopolis Academia (Solucionáticos), estructurada sobre las especificaciones y estándares abiertos del W3C, el estándar vivo de WHATWG y la documentación oficial de código abierto. © Todos los derechos reservados.