¿Por qué este capítulo? Todavía veo a desarrolladores junior entregar código con
vary loopsformanuales en proyectos OWL — funciona, pero pelea contra el lenguaje sobre el que OWL fue diseñado, y se nota en cada code review. ¿Qué trata de resolver Odoo con esto? OWL 2.0 (y el propio código JS de Odoo) asume que te sientes cómodo con JavaScript moderno —let/const, funciones de flecha, template literals y métodos de array no son extras opcionales, son la base en la que está escrito el propio código fuente del framework. Aplicación en la vida real: Cada.map()/.filter()que vayas a escribir para transformar resultados del ORM en algo que un template pueda renderizar, y cada template literal que uses para armar una clase dinámica o un mensaje, sale directamente de este capítulo.
Ahora que conoces tus herramientas, es hora de aprender el lenguaje. JavaScript moderno (a menudo llamado ES6 o más nuevo) introdujo características poderosas que ahora son el estándar para el desarrollo web. OWL está construido completamente sobre estos conceptos modernos.
Este capítulo y el siguiente son tu campo de entrenamiento de JavaScript. Nos enfocaremos en los elementos absolutamente esenciales que usarás todos los días al escribir componentes OWL. Para mantener las cosas claras, todos los ejemplos aquí son JavaScript puro, sin código de Odoo u OWL involucrado. Construyamos los cimientos.
Variables Re-imaginadas: let y const
Durante años, JavaScript tuvo solo una forma de declarar una variable: var. Era confuso y tenía comportamientos extraños. JavaScript moderno arregló esto dándonos dos nuevas palabras clave mucho más predecibles: let y const.
let es para variables cuyo valor podría necesitar cambiar más tarde.
const es para constantes—variables cuyo valor, una vez asignado, nunca cambiará.
Regla general: Siempre usa const por defecto. Solo usa let si sabes que necesitarás reasignar la variable. Casi nunca necesitarás usar el viejo var.
// Usa let para una variable que cambiará.
let score = 0;
console.log(score); // Salida: 0
score = 10; // Esto está permitido.
console.log(score); // Salida: 10
// Usa const para un valor que no debería cambiar.
const playerName = "Alice";
console.log(playerName); // Salida: "Alice"
// Si tratas de cambiar un const, obtienes un error.
// playerName = "Bob"; // ¡Esto causaría un error!
La ventaja principal de let y const es que tienen "alcance de bloque", lo que significa que solo existen dentro de las llaves {} en las que están definidas. Esto hace que tu código sea mucho más fácil de razonar y previene muchos errores comunes.
if (true) {
const message = "Dentro del bloque";
console.log(message); // Funciona bien
}
// console.log(message); // ¡Error! message no existe fuera del bloque
Funciones de Flecha: Una Sintaxis Más Limpia
Las funciones son los bloques de construcción de cualquier aplicación. Las funciones de flecha (=>) proporcionan una forma más corta y limpia de escribirlas.
Aquí hay una función tradicional:
function add(a, b) {
return a + b;
}
Y aquí está la misma función exacta escrita como una función de flecha:
const add = (a, b) => {
return a + b;
};
Aún mejor, si tu función es solo una línea, puedes hacerla aún más corta. El return es implícito:
const subtract = (a, b) => a - b;
console.log(subtract(10, 4)); // Salida: 6
Para funciones con solo un parámetro, puedes omitir los paréntesis:
const double = x => x * 2;
const greet = name => `¡Hola, ${name}!`;
console.log(double(5)); // Salida: 10
console.log(greet("Alice")); // Salida: ¡Hola, Alice!
Verás funciones de flecha usadas en todas partes en JavaScript moderno y código OWL porque son concisas y ayudan a evitar algunos problemas complicados con la palabra clave this.
Template Literals: Construyendo Strings con Poder
Una de las características más útiles de JavaScript moderno son los template literals, escritos con comillas inversas (`) en lugar de comillas regulares. Hacen que crear strings dinámicos sea mucho más fácil y legible.
La Forma Antigua: Concatenación de Strings
Antes de los template literals, construir strings dinámicos era torpe:
const name = "Juan Pérez";
const age = 35;
const department = "Ventas";
// La forma antigua y dolorosa
const message = "Hola, " + name + "! Tienes " + age + " años y trabajas en " + department + ".";
console.log(message);
// Salida: Hola, Juan Pérez! Tienes 35 años y trabajas en Ventas.
La Forma Moderna: Template Literals
Con template literals, usas comillas inversas y ${} para embeber expresiones directamente en el string:
const name = "Juan Pérez";
const age = 35;
const department = "Ventas";
// La forma moderna y limpia
const message = `Hola, ${name}! Tienes ${age} años y trabajas en ${department}.`;
console.log(message);
// Salida: Hola, Juan Pérez! Tienes 35 años y trabajas en Ventas.
Por Qué los Template Literals Son Perfectos para OWL
Los template literals son especialmente útiles en el desarrollo de OWL por varias razones:
1. Clases CSS Dinámicas:
const isActive = true;
const priority = "high";
const cssClass = `task-item ${isActive ? 'active' : 'inactive'} priority-${priority}`;
console.log(cssClass); // Salida: task-item active priority-high
2. Construyendo URLs para llamadas RPC:
const recordId = 123;
const model = "res.partner";
const url = `/web/dataset/call_kw/${model}/read`;
const searchUrl = `/web/dataset/search_read?model=${model}&ids=[${recordId}]`;
3. Strings multi-línea (genial para depuración):
const debugInfo = `
Estado del Componente:
- Nombre: ${this.state.name}
- Activo: ${this.state.isActive}
- Registros: ${this.state.records.length}
`;
console.log(debugInfo);
4. Generación dinámica de HTML (aunque usarás templates para esto en OWL):
const createNotification = (type, message) => {
return `<div class="alert alert-${type}">
<strong>${type.toUpperCase()}:</strong> ${message}
</div>`;
};
Template Literals vs. Strings Regulares
Aquí hay una comparación para mostrar cuándo usar cada uno:
// Usa strings regulares para texto estático
const staticMessage = "Bienvenido a Odoo";
const errorCode = "ERR_404";
// Usa template literals cuando necesites contenido dinámico
const welcomeMessage = `¡Bienvenido de vuelta, ${userName}!`;
const apiEndpoint = `${baseUrl}/api/v1/${resourceType}/${id}`;
const debugOutput = `Procesando registro ${recordId} en ${new Date()}`;
// Usa template literals para contenido multi-línea
const emailTemplate = `
Estimado ${customerName},
Su pedido #${orderNumber} ha sido ${status}.
Saludos cordiales,
El Equipo de Odoo
`;
Entendiendo Objetos: La Piedra Angular de los Datos
Un objeto es una colección de datos relacionados y funcionalidad, almacenados como pares clave-valor. Esta es la forma más común en que estructurarás tus datos en JavaScript. Piénsalo como una tarjeta de contacto.
const contact = {
// clave: valor
name: "Juan Pérez",
email: "[email protected]",
age: 35,
isClient: true,
// Los objetos también pueden tener funciones (llamadas métodos)
sayHello() {
console.log("¡Hola!");
}
};
// Puedes acceder a los datos usando notación de punto.
console.log(contact.name); // Salida: "Juan Pérez"
// Y llamar métodos de la misma manera.
contact.sayHello(); // Salida: "¡Hola!"
También puedes acceder a propiedades usando notación de corchetes, lo cual es útil cuando el nombre de la propiedad es dinámico:
const propertyName = "email";
console.log(contact[propertyName]); // Salida: "[email protected]"
// Esto es especialmente útil con template literals
const fieldName = "name";
const value = contact[fieldName];
const message = `El campo ${fieldName} contiene: ${value}`;
Los objetos son fundamentales para OWL porque el state de un componente siempre es un objeto.
Dominando Arrays: Trabajando con Listas
Un array es una lista ordenada de elementos. Podría ser una lista de números, strings, o incluso otros objetos.
const tasks = [
{ id: 1, text: "Escribir capítulo 3", completed: true },
{ id: 2, text: "Crear ejemplos de código", completed: false },
{ id: 3, text: "Beber café", completed: false }
];
Constantemente necesitarás transformar o filtrar arrays para mostrarlos en tu UI. JavaScript moderno nos da métodos poderosos para hacer esto sin escribir bucles complicados. Aquí están los tres que usarás más a menudo:
.map() - Para Transformar un Array
El método .map() crea un nuevo array transformando cada elemento en el array original. Es perfecto para cuando quieres crear una lista de elementos de UI desde una lista de datos.
// Obtengamos un array de solo las descripciones de las tareas.
const taskTexts = tasks.map(task => task.text);
console.log(taskTexts);
// Salida: ["Escribir capítulo 3", "Crear ejemplos de código", "Beber café"]
// Usando template literals con map para transformaciones más complejas
const taskSummaries = tasks.map(task => {
const status = task.completed ? "Hecho" : "Pendiente";
return `${task.text} - ${status}`;
});
console.log(taskSummaries);
// Salida: ["Escribir capítulo 3 - Hecho", "Crear ejemplos de código - Pendiente", ...]
.filter() - Para Seleccionar Elementos de un Array
El método .filter() crea un nuevo array conteniendo solo los elementos que pasan una cierta prueba.
// Obtengamos solo las tareas que aún no están completadas.
const incompleteTasks = tasks.filter(task => task.completed === false);
console.log(incompleteTasks);
// Salida: Un array con los objetos "Crear ejemplos de código" y "Beber café".
// Filtrado más complejo con template literals para logging
const highPriorityTasks = tasks.filter(task => {
const isIncomplete = !task.completed;
const isImportant = task.text.includes("café"); // ¡Muy importante!
if (isIncomplete && isImportant) {
console.log(`Encontrada tarea importante: ${task.text}`);
return true;
}
return false;
});
.find() - Para Obtener un Solo Elemento de un Array
El método .find() devuelve el primer elemento en un array que pasa una prueba. Es útil para encontrar un objeto específico por su ID.
// Encontremos la tarea con id 2.
const specificTask = tasks.find(task => task.id === 2);
console.log(specificTask);
// Salida: { id: 2, text: "Crear ejemplos de código", completed: false }
// Usando find con lógica más compleja
const findTaskByPartialText = (searchText) => {
const foundTask = tasks.find(task =>
task.text.toLowerCase().includes(searchText.toLowerCase())
);
if (foundTask) {
console.log(`Encontrada tarea: ${foundTask.text}`);
return foundTask;
} else {
console.log(`No se encontró tarea que contenga "${searchText}"`);
return null;
}
};
findTaskByPartialText("café"); // Encontrará la tarea "Beber café"
Juntándolo Todo: Un Ejemplo del Mundo Real
Combinemos todos estos conceptos en un ejemplo práctico que podrías encontrar en el desarrollo de OWL:
// Datos de muestra que podrían venir de un modelo de Odoo
const customers = [
{ id: 1, name: "Acme Corp", email: "[email protected]", isActive: true, totalOrders: 25 },
{ id: 2, name: "Tech Solutions", email: "[email protected]", isActive: false, totalOrders: 8 },
{ id: 3, name: "Global Industries", email: "[email protected]", isActive: true, totalOrders: 42 }
];
// Encontrar clientes activos con más de 10 pedidos
const vipCustomers = customers
.filter(customer => customer.isActive && customer.totalOrders > 10)
.map(customer => {
// Crear un resumen usando template literals
const status = customer.isActive ? "Activo" : "Inactivo";
const summary = `${customer.name} (${customer.email}) - ${status} - ${customer.totalOrders} pedidos`;
return {
...customer, // El operador spread copia todas las propiedades del objeto original a este nuevo objeto, así no hace falta listarlas una por una
summary: summary,
displayName: `VIP: ${customer.name}`
};
});
console.log("Clientes VIP:");
vipCustomers.forEach(customer => {
console.log(customer.summary);
});
// Esto podría mostrar:
// Clientes VIP:
// Acme Corp ([email protected]) - Activo - 25 pedidos
// Global Industries ([email protected]) - Activo - 42 pedidos
Errores Comunes
Confundir .map(), .filter() y .forEach(). .map() y .filter() devuelven un array nuevo y están pensados para usarse con ese resultado (asignado, renderizado, encadenado). .forEach() devuelve undefined—solo sirve para ejecutar efectos secundarios (como console.log) en cada elemento, nunca para construir una lista nueva.
Mutar el array u objeto original. tasks.push(...) o contact.name = "..." cambia los datos originales directamente. En OWL, mutar un objeto o array plano de esta forma no dispara un re-render y puede causar bugs difíciles de rastrear. Prefiere crear arrays/objetos nuevos (como ya hacen .map() y .filter()) o usar useState, que veremos en el Capítulo 7.
Usar == en vez de ===. == convierte los tipos antes de comparar ("5" == 5 da true), lo que provoca bugs confusos. Usa siempre === (y !==) salvo que tengas una razón específica para no hacerlo.
Olvidar que const impide la reasignación, no la mutación. const contact = {...} significa que no puedes hacer contact = otroObjeto, pero sí puedes seguir haciendo contact.name = "Nuevo Nombre". const solo bloquea la variable en sí, no el contenido del objeto.
Ejercicios
- Escribe una función de flecha
formatPrice(amount, currency)que use un template literal para devolver un string como"$42.50"(o"42.50 USD"si prefieres otro formato). - Dado
const numbers = [4, 15, 8, 23, 16, 42];, usa.filter()para obtener solo los números mayores a 10, y luego.map()para duplicar cada uno. Intenta escribirlo como una sola expresión encadenada. - Usando el array
tasksde este capítulo, usa.find()para localizar la tarea conid: 3, y registra un mensaje que indique si está completada o no.
TL;DR: let/const, funciones de flecha, template literals y los métodos de array .map()/.filter()/.find() son el vocabulario de JavaScript moderno en el que está escrito el propio código fuente de OWL — y cada ejemplo de este libro.
Pruébalo tú mismo: El ejemplo de este capítulo está disponible como addon instalable de Odoo 19: simplifyit_owl_book_ch3_ex1. Las instrucciones de instalación están en el README del repositorio.
¿Qué Sigue?
Dominar let, const, funciones de flecha, template literals, objetos, y estos métodos de array te dará una base sólida para construir componentes OWL poderosos y limpios. En el siguiente capítulo, exploraremos conceptos más avanzados — clases, destructuring, módulos y async/await — que son cruciales para entender cómo funcionan internamente los componentes OWL.