Saltar al contenido.
Blog de Enrique Stolar

Blog


Publicado el 26/07/2026 | Autor: Enrique Stolar

Introduccion a POO: objetos, clases y responsabilidades

Introduccion a POO: objetos, clases y responsabilidades

Serie: Desarrollo de Interfaces

Curso: Desarrollo de Interfaces 1

Capítulo 23: Introducción a POO: objetos, clases y responsabilidades

Capítulo anterior: Consultas preparadas, seguridad y validación en backend

Capítulo siguiente: Separar rutas, controladores y modelos en Node.js

Hasta ahora hemos construido una ruta bastante completa: variables, condicionales, bucles, funciones, DOM, formularios, arreglos, objetos, CRUD, JSON, APIs, SQL, MySQL, consultas preparadas y validación en backend. Ya no estamos jugando solo con fragmentos aislados. Estamos empezando a construir aplicaciones.

Y cuando una aplicación crece, aparece un problema silencioso: el código empieza a dispersarse. Una función valida datos, otra guarda información, otra arma una respuesta, otra decide estados, otra transforma objetos. Todo funciona, pero cuesta encontrar dónde vive cada responsabilidad.

La programación orientada a objetos, o POO, no es una varita mágica ni una obligación para todo proyecto. Es una forma de pensar el código alrededor de unidades que tienen datos y comportamientos. En JavaScript podemos trabajar con objetos literales, funciones constructoras y clases. En este capítulo nos concentraremos en la idea central: usar objetos y clases para organizar mejor la lógica.

Qué aprenderás hoy

  • Qué significa pensar en objetos dentro de una aplicación.
  • Qué diferencia hay entre un objeto literal y una clase.
  • Cómo crear una clase con constructor y métodos.
  • Cómo asignar responsabilidades sin mezclar todo en una sola función.
  • Cómo aplicar POO a un ejemplo de proyectos de clase.
  • Qué errores evitar cuando recién empiezas con clases.
Idea clave

La POO no consiste en escribir class por escribir class. Consiste en decidir qué parte del sistema debe saber, hacer y proteger cada cosa.

Primero: ya conoces objetos

Antes de hablar de clases, recordemos algo importante: en JavaScript ya usamos objetos. Un objeto agrupa propiedades y valores relacionados.

const project = {
  id: 1,
  title: 'Portafolio personal',
  status: 'review',
  team: 'Equipo Aurora'
};

Este objeto representa un proyecto. Tiene datos. Podemos leerlos, modificarlos y enviarlos a una interfaz o a una API.

console.log(project.title);
project.status = 'published';

El problema aparece cuando además necesitamos comportamiento. Por ejemplo: validar si el proyecto puede publicarse, cambiar su estado, preparar un resumen o calcular si está atrasado. Podríamos hacerlo con funciones sueltas.

function canPublish(project) {
  return project.title.trim().length >= 3 && project.status === 'review';
}

Esto está bien. De hecho, muchas veces es suficiente. Pero si varias funciones trabajan siempre con el mismo tipo de dato, quizá conviene agrupar datos y comportamiento en una misma unidad.

Una clase como molde

Una clase puede entenderse como un molde para crear objetos con la misma estructura y los mismos métodos. En JavaScript, una clase se declara con la palabra class. El método constructor se ejecuta cuando creamos un nuevo objeto con new.

class Project {
  constructor(id, title, status, team) {
    this.id = id;
    this.title = title;
    this.status = status;
    this.team = team;
  }

  canPublish() {
    return this.title.trim().length >= 3 && this.status === 'review';
  }
}

Ahora podemos crear instancias de esa clase:

const project = new Project(
  1,
  'Portafolio personal',
  'review',
  'Equipo Aurora'
);

console.log(project.canPublish());

La palabra this apunta al objeto actual. En este caso, this.title es el título del proyecto que fue creado con esa clase.

Buena práctica

Una clase debería representar una idea clara del dominio. Si todo termina dentro de una clase gigante llamada App, probablemente no estamos organizando: solo estamos escondiendo el desorden.

Responsabilidades: la parte más importante

El concepto más valioso de este capítulo no es la sintaxis. Es la responsabilidad. Una responsabilidad responde a una pregunta sencilla: ¿de qué debería encargarse esta parte del código?

Por ejemplo, una clase Project puede saber:

  • Cuál es su título.
  • Cuál es su estado actual.
  • Si tiene datos mínimos para publicarse.
  • Cómo cambiar de estado de forma válida.

Pero no debería saber todo. Un proyecto no debería encargarse de abrir una conexión a MySQL, renderizar una tarjeta HTML y decidir el color exacto del botón. Esas son responsabilidades distintas.

Mejorando la clase Project

Agreguemos métodos para cambiar estado sin permitir cualquier valor. Esto conecta con el capítulo anterior, donde hablamos de listas permitidas y validación.

class Project {
  constructor({ id, title, status = 'draft', team }) {
    this.id = id;
    this.title = title;
    this.status = status;
    this.team = team;
  }

  canPublish() {
    return this.title.trim().length >= 3 && this.status === 'review';
  }

  sendToReview() {
    if (this.status !== 'draft') {
      throw new Error('Solo un proyecto en borrador puede pasar a revision.');
    }

    this.status = 'review';
  }

  publish() {
    if (!this.canPublish()) {
      throw new Error('El proyecto no cumple las condiciones para publicarse.');
    }

    this.status = 'published';
  }
}

Este diseño evita cambiar estados sin control desde cualquier parte del sistema. En vez de hacer project.status = 'published' directamente, usamos un método que expresa intención y protege reglas.

Usar la clase en una ruta

En un backend con Express, podríamos construir una instancia a partir de los datos enviados por el cliente:

app.post('/projects', asyncHandler(async (request, response) => {
  const project = new Project({
    title: request.body.title,
    team: request.body.team
  });

  project.sendToReview();

  const [result] = await pool.execute(
    `INSERT INTO projects (title, team_name, status)
     VALUES (?, ?, ?)`,
    [project.title, project.team, project.status]
  );

  response.status(201).json({
    id: result.insertId,
    title: project.title,
    team: project.team,
    status: project.status
  });
}));

Este ejemplo todavía es simple. Más adelante separaremos rutas, controladores y modelos. Por ahora lo importante es notar que la clase concentra reglas del proyecto, mientras la ruta se ocupa de recibir una solicitud y responder.

Objeto para la interfaz

La POO no solo sirve en backend. También puede ayudarnos en la interfaz. Supongamos que queremos renderizar tarjetas de proyectos. Podemos crear un objeto que prepare el texto de estado para mostrarlo al usuario.

class ProjectViewModel {
  constructor(project) {
    this.project = project;
  }

  statusLabel() {
    const labels = {
      draft: 'Borrador',
      review: 'En revision',
      published: 'Publicado'
    };

    return labels[this.project.status] || 'Sin estado';
  }

  summary() {
    return `${this.project.title} - ${this.statusLabel()}`;
  }
}

Un ViewModel no guarda datos en la base. Su responsabilidad es adaptar datos para la interfaz. Esta separación ayuda mucho cuando el proyecto crece.

Errores comunes al empezar con POO

  • Crear clases para todo, incluso cuando una función simple bastaba.
  • Hacer clases enormes que mezclan base de datos, interfaz, validación y estilos.
  • Usar nombres genéricos como Manager, Helper o Utils sin responsabilidad clara.
  • Guardar datos públicos que cualquier parte modifica sin control.
  • Confundir clase con objeto: la clase es el molde; el objeto es la instancia creada.
Cuidado

POO mal aplicada puede volver el código más difícil. No se trata de decorar el proyecto con clases, sino de reducir confusión y duplicación.

Cuándo usar clases y cuándo no

Una clase puede ser útil cuando tienes una entidad con datos y reglas propias: usuario, proyecto, producto, carrito, curso, reserva, pedido. También ayuda cuando necesitas crear muchos objetos con la misma estructura.

Una función puede ser mejor cuando solo transformas un dato, formateas un texto, validas un campo o realizas una operación puntual sin estado interno. En JavaScript moderno conviven ambos estilos. Un buen desarrollador no se casa con una sola herramienta: elige la que hace más claro el código.

Reto práctico

Reto

Crea una clase StudentProject con propiedades title, studentName, course y score. Agrega métodos para validar si el proyecto está aprobado, generar un resumen y cambiar el puntaje solo si está entre 0 y 20.

Variante extra: crea una clase StudentProjectViewModel que devuelva una etiqueta visual: “Excelente”, “Aprobado”, “En proceso” o “Necesita mejorar”.

Conclusión

La programación orientada a objetos nos invita a pensar en piezas con sentido. Cada objeto debería representar algo reconocible del problema y cada clase debería tener una responsabilidad comprensible. Cuando eso ocurre, el código deja de sentirse como una lista interminable de instrucciones y empieza a parecerse más a un sistema organizado.

En el próximo capítulo llevaremos esta idea al backend: separaremos rutas, controladores y modelos para que una API de Node.js no dependa de un solo archivo enorme.

Referencias consultadas

Compartir este artículo: