Programación funcional en la práctica: acciones, cálculos y datos con código real

Nada de citas académicas inventadas: programación funcional explicada con código real, separando acciones, cálculos y datos para escribir funciones que se puedan testear sin mocks.

Desarrollador de software trabajando en una línea de código
13 mar 20254 min de lectura
Actualizado el 2 sept 2026

Tenés una función que se llama addToCart y hace tres cosas en el mismo lugar: agrega el ítem a un array, llama a una API para persistir el carrito, y dispara un evento de analytics. El test unitario, si existe, mockea las tres cosas. Cambiás una y rompés las otras dos sin darte cuenta. Ese es el problema que la programación funcional resuelve, no con matemática abstracta, sino separando qué parte de tu código hace algo (efecto secundario) de qué parte solo calcula algo (transforma datos).

Acciones, cálculos y datos: la distinción que importa

Una acción es cualquier función cuyo resultado depende de cuándo o cuántas veces se ejecuta, o que toca algo fuera de sí misma. saveUser(user) que hace un POST a tu API es una acción: llamarla dos veces crea dos registros, llamarla sin red falla. Date.now() también es una acción, aunque no mute nada: el resultado depende del momento exacto en que corre.

Un cálculo es una función pura: misma entrada, misma salida, sin tocar nada afuera. const total = items => items.reduce((sum, item) => sum + item.price * item.qty, 0) es un cálculo. Da igual si lo llamás una vez o mil veces, en un test o en producción: para el mismo array de items, el resultado es siempre el mismo.

Los datos son los hechos, sin comportamiento: { id: 'sku-104', price: 25000, qty: 2 }. En JavaScript es tentador meterles comportamiento con clases y métodos, pero cuanto más los tratás como estructuras inertes, más fácil es razonar sobre ellos, serializarlos y compararlos en un test con un simple toEqual.

Refactorizando una acción real

Volvamos a addToCart. Así arranca, mezclando las tres cosas: function addToCart(item) { cart.push(item); localStorage.setItem('cart', JSON.stringify(cart)); analytics.track('item_added', { sku: item.id }); }. Esta función no se puede testear sin mockear localStorage y analytics, y tampoco se puede reusar en un contexto donde todavía no querés persistir nada, por ejemplo al precargar un carrito desde el servidor.

El primer paso es separar el cálculo del resto: qué es el carrito nuevo, no cómo se persiste ni qué se trackea. const addItem = (cart, item) => [...cart, item]. Esta versión es un cálculo puro: recibe el carrito actual y el ítem, devuelve un carrito nuevo, no toca nada externo. La podés llamar mil veces en un test sin configurar un solo mock.

Las acciones quedan afuera, explícitas, en la capa que orquesta: function handleAddToCart(item) { cart = addItem(cart, item); persistCart(cart); analytics.track('item_added', { sku: item.id }); }. persistCart y analytics.track siguen siendo acciones, eso no cambia, los efectos secundarios existen porque el software tiene que hacer cosas en el mundo real. Lo que cambió es que el cálculo del nuevo estado del carrito está aislado, es testeable sin mocks, y se puede reusar en cualquier lugar donde necesites la misma lógica sin arrastrar los efectos.

Dónde se nota la diferencia en un sistema real

Esta separación importa más a medida que el sistema crece. Un pipeline de checkout con validación de stock, cálculo de impuestos, aplicación de descuentos y cálculo de envío se vuelve manejable cuando cada uno de esos pasos es un cálculo puro que se encadena, y las acciones (cobrar la tarjeta, actualizar el inventario, mandar el email de confirmación) quedan en los bordes, ejecutándose una sola vez al final, en el orden correcto. Si mezclás todo, cada cambio en la lógica de descuentos te obliga a revisar si rompiste el cobro con la tarjeta, porque están en la misma función.

El mismo criterio aplica en backend con lógica de negocio compleja, en transformación de datos para un pipeline de analytics, o en el estado de un frontend con Redux o cualquier store: si podés mover la lógica de qué debería pasar fuera de cómo se ejecuta y con qué efectos, el código resultante es más fácil de testear sin mocks, más fácil de paralelizar sin condiciones de carrera, y más fácil de leer porque cada función hace una sola cosa que se explica en una oración.

La programación funcional no te obliga a usar Haskell ni a evitar clases por completo. La parte que de verdad te cambia como developer senior es más simple y más práctica: antes de escribir una función, preguntáte si estás calculando algo o si estás haciendo algo, y si es lo segundo, preguntáte si de verdad necesita estar mezclado con el cálculo. La mayoría de las veces, no.

ESCRITO POR

Desarrollador de Software
Cristóbal MarinkovicSoftware Developer
COMPARTIR