MSc. Juan Andres Segreda Johanning
Sysco 2025
El código limpio reduce significativamente el tiempo necesario para entender, modificar y extender funcionalidades existentes.
La claridad y estructura del código limpio facilita la identificación temprana de errores y previene defectos.
Los equipos pueden iterar más rápido cuando el código es predecible y bien organizado.
La diferencia entre código desordenado y código limpio no es solo estética: impacta directamente en la productividad del equipo, la velocidad de entrega y la calidad del producto final.
"El código se lee mucho más de lo que se escribe. Optimizar para la lectura es optimizar para el éxito del proyecto."
¿Dónde prefieres trabajar? La respuesta es obvia cuando consideras el costo real del código sucio.

El código debe leerse como prosa bien escrita. Otros desarrolladores deben poder entender la intención sin esfuerzo adicional.
Cada componente tiene una responsabilidad específica y bien definida, facilitando pruebas y reutilización.
Los nombres de variables, funciones y clases revelan su propósito sin necesidad de comentarios extensos.
Facilita el trabajo en equipo al establecer patrones consistentes y predecibles que todos pueden seguir.
Desarrollados por Robert C. Martin (Uncle Bob) como guía para el diseño orientado a objetos.
Perfectamente aplicables a JavaScript moderno, React y el ecosistema Node.js.
Mejoran significativamente la calidad, testabilidad y escalabilidad del código.

Cinco principios fundamentales que transformarán tu código en una arquitectura sólida y mantenible.
"Cada módulo o componente debe tener una única razón para cambiar"
Cada componente se enfoca en hacer una cosa específica y la hace bien.
Un componente SurahCard solo muestra datos, no los obtiene de la API.
Separar lógica de negocio y acceso a base de datos en clases distintas.
// Componente que hace demasiado
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// Obtiene datos
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
// Valida datos
const isValidEmail = (email) => {
return /\S+@\S+\.\S+/.test(email);
};
// Formatea fechas
const formatDate = (date) => {
return new Date(date).toLocaleDateString();
};
// Renderiza UI compleja
return (
<div className="profile">
{/* Lógica de renderizado extensa */}
</div>
);
}
// Hook personalizado para datos
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
UserService.getUser(userId)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading };
}
// Utilidades separadas
const validators = {
isValidEmail: (email) => /\S+@\S+\.\S+/.test(email)
};
const formatters = {
formatDate: (date) => new Date(date).toLocaleDateString()
};
// Componente enfocado solo en UI
function UserProfile({ userId }) {
const { user, loading } = useUser(userId);
if (loading) return <LoadingSpinner />;
return <UserCard user={user} />;
}
El resultado: código más limpio, testeable y mantenible donde cada pieza tiene un propósito específico.
"El software debe estar abierto para extensión, pero cerrado para modificación."
El Principio de Abierto/Cerrado (OCP) establece que las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas a la extensión, pero cerradas a la modificación. Esto significa que deberíamos poder añadir nuevas funcionalidades a un sistema sin alterar el código existente que ya funciona correctamente. Su objetivo principal es asegurar que los cambios futuros no rompan el código que ya ha sido probado y puesto en producción.
En React, el OCP se aplica creando componentes que aceptan props o render props para extender su comportamiento, en lugar de modificar el componente original.
Button genérico que acepta una prop variant (primary, secondary, danger) o onClick, permitiendo nuevos comportamientos sin modificar la clase base del botón.Button cada vez que se necesite un nuevo estilo o acción.Para React Native, el OCP se manifiesta en la creación de módulos nativos que pueden ser añadidos y utilizados sin necesidad de alterar el código del core de la aplicación ni de otros módulos existentes.
En Node.js, el OCP es fundamental en la arquitectura de aplicaciones web con frameworks como Express, donde se usa middleware y sistemas de plugins.
Beneficios: Facilita el mantenimiento, reduce errores y permite un desarrollo de software más robusto y escalable.
// Button.jsx - Requiere modificación para cada nuevo tipo
function Button({ type, children, onClick }) {
let className = "button";
// Si se añade un nuevo tipo de botón (ej. "warning"),
// este componente debe ser modificado.
if (type === "primary") {
className += " button-primary";
} else if (type === "secondary") {
className += " button-secondary";
} else if (type === "danger") {
className += " button-danger";
}
return (
<button className={className} onClick={onClick}>
{children}
</button>
);
}
// Uso:
// <Button type="primary" onClick={...}>Enviar</Button>
// <Button type="secondary" onClick={...}>Cancelar</Button>
// BaseButton.jsx - Abierto a extensión, cerrado a modificación
// Componente genérico que solo define el comportamiento base
function BaseButton({ children, onClick, className = "" }) {
return (
<button className={`button ${className}`} onClick={onClick}>
{children}
</button>
);
}
// PrimaryButton.jsx - Extiende BaseButton sin modificarlo
function PrimaryButton({ children, onClick }) {
return (
<BaseButton className="button-primary" onClick={onClick}>
{children}
</BaseButton>
);
}
// DangerButton.jsx - Otro tipo de botón
function DangerButton({ children, onClick }) {
return (
<BaseButton className="button-danger" onClick={onClick}>
{children}
</BaseButton>
);
}
// Uso:
// <PrimaryButton onClick={...}>Guardar</PrimaryButton>
// <DangerButton onClick={...}>Eliminar</DangerButton>
// Para un nuevo botón, se crea un nuevo componente (ej. WarningButton)
// sin tocar BaseButton.
La clave: componentes flexibles y reusables que se extienden sin necesidad de modificación directa, permitiendo un desarrollo más ágil y menos propenso a errores.
"Los objetos de una clase derivada deben poder reemplazar objetos de la clase base sin alterar el funcionamiento del programa."
El Principio de Sustitución de Liskov (LSP) es una extensión del Principio de Abierto/Cerrado (OCP), y establece una regla fundamental para las jerarquías de herencia o composición. Postula que los objetos de un tipo base deben poder ser reemplazados por objetos de un tipo derivado sin alterar ninguna de las propiedades deseables del programa (precisión, tarea realizada, consumo de recursos, etc.). Esto asegura que las subclases no solo cumplan con la interfaz, sino también con el comportamiento esperado de su clase base, manteniendo la coherencia y predictibilidad del sistema.
El LSP garantiza que cualquier función que opere con una clase base también pueda operar con sus subclases sin necesidad de conocer los detalles específicos de cada una. Esto promueve la reutilización de código y facilita el mantenimiento.
B es una subclase de A, entonces un objeto de B puede ser usado donde se espera un objeto de A.En React, el LSP se manifiesta cuando un componente superior espera ciertas propiedades o comportamientos de sus hijos, y estos hijos (que pueden ser de diferentes "tipos" o implementaciones) los cumplen consistentemente.
<List> que acepta una prop itemComponent. Podemos pasarle <ProductItem>, <ServiceItem> o <UserItem>, siempre y cuando todos implementen la interfaz esperada por List (ej., una prop data).<ProductItem> requiere una prop price y <UserItem> no la tiene, y el componente <List> espera price, se viola LSP si no se maneja adecuadamente.Aunque JavaScript no tiene herencia clásica estricta como otros lenguajes, el concepto se aplica a través de la herencia prototípica o el uso de clases y mixins.
Animal con un método makeSound(), y subclases como Dog y Cat que también tienen makeSound(), esperamos que cualquier objeto de tipo Dog o Cat pueda reemplazar a Animal y su método makeSound() funcione sin romper el programa.Cat, en lugar de makeSound(), implementara meow(), violaría el LSP, ya que una función que espera Animal.makeSound() no funcionaría con un objeto Cat.El LSP subraya la importancia de mantener un contrato (tanto implícito como explícito) entre la clase base y sus subclases. Este contrato incluye no solo las firmas de los métodos, sino también las precondiciones, postcondiciones e invariantes.
El impacto: Código más robusto, predecible y fácil de extender, evitando efectos secundarios inesperados en sistemas complejos.
"Los clientes no deberían verse obligados a depender de interfaces que no utilizan."
El Principio de Segregación de Interfaces (ISP) es el cuarto principio de SOLID y se centra en la importancia de diseñar interfaces pequeñas, específicas y cohesivas en lugar de interfaces grandes y monolíticas. Este principio aboga por dividir interfaces grandes en interfaces más pequeñas y manejables, de modo que las clases que las implementen solo necesiten depender de los métodos que realmente utilizan.
El ISP promueve que las interfaces no deben ser "gordas". En lugar de una interfaz universal con muchos métodos, es mejor tener varias interfaces más pequeñas y focalizadas, cada una con un propósito bien definido. Esto reduce el acoplamiento y mejora la flexibilidad.
En React, el ISP se aplica al diseñar los "contratos" de props que esperan los componentes. Un componente no debería requerir un objeto de props que contenga propiedades que no va a utilizar para su funcionamiento.
UserComponent que recibe un objeto userData con todas las propiedades posibles del usuario (nombre, email, dirección, preferencias, historial de compras, etc.), es mejor pasar solo las props que el componente realmente necesita.UserNameDisplay solo muestra el nombre, su interfaz de props debería ser { name: string }, no { userData: UserObject }.En el desarrollo de APIs con Node.js, el ISP se manifiesta al diseñar los endpoints y las interfaces de los servicios. Un cliente de API no debería verse obligado a solicitar o procesar datos que no le son relevantes.
/users/fullData que devuelve toda la información de un usuario, se pueden crear interfaces segregadas como /users/{id}/profile, /users/{id}/orders, o /users/{id}/settings.Al segregar interfaces, fomentamos interfaces más cohesivas, donde cada una tiene una única responsabilidad bien definida. Esto conduce a un código más modular, fácil de probar y reutilizable.
El resultado: Un sistema más flexible, con componentes que dependen menos entre sí, lo que facilita el desarrollo, el mantenimiento y la evolución del software.
Aquí, el componente UserProfile recibe todas las propiedades de un usuario, aunque solo necesita algunas para su visualización. Esto lo acopla a la estructura completa del objeto User.
{/*
Este componente es "gordo" porque espera props que no usa.
Si la estructura de `User` cambia (ej., `address` ya no existe),
este componente podría verse afectado sin necesidad.
*/}
function UserProfile({ id, name, email, address, preferences, orderHistory }) {
return (
<div>
<h2>{name}</h2>
<p>Email: {email}</p>
{/* Asumimos que solo mostramos el nombre y el email aquí */}
{/* `address`, `preferences`, `orderHistory` no se usan */}
</div>
);
}
// Uso:
// <UserProfile
// id="1"
// name="Juan Pérez"
// email="juan@example.com"
// address={{ street: "Calle Falsa 123" }}
// preferences={{ theme: "dark" }}
// orderHistory={[]}
// />
Dividimos la funcionalidad en componentes más pequeños, cada uno con un conjunto mínimo de props necesarias. Esto hace que sean más reutilizables y menos propensos a cambios externos.
{/* Componente para mostrar solo el nombre del usuario */}
function UserNameDisplay({ name }) {
return <h2>{name}</h2>;
}
{/* Componente para mostrar el email del usuario */}
function UserEmailDisplay({ email }) {
return <p>Email: {email}</p>;
}
{/* Componente para mostrar las preferencias del usuario (si las necesita) */}
function UserPreferencesDisplay({ preferences }) {
return (
<div>
<p>Preferencias: {preferences.theme}</p>
</div>
);
}
{/*
Un componente contenedor que ensambla las partes,
pasando solo las props necesarias a cada subcomponente.
*/}
function UserProfileContainer({ user }) {
return (
<div>
<UserNameDisplay name={user.name} />
<UserEmailDisplay email={user.email} />
{/* Otros componentes específicos aquí, si son necesarios */}
{user.preferences && <UserPreferencesDisplay preferences={user.preferences} />}
</div>
);
}
// Uso:
// const userData = {
// id: "1",
// name: "Juan Pérez",
// email: "juan@example.com",
// address: { street: "Calle Falsa 123" },
// preferences: { theme: "dark" },
// orderHistory: [],
// };
// <UserProfileContainer user={userData} />
Beneficios: Al tener interfaces de props más pequeñas y focalizadas, los componentes son más reutilizables, más fáciles de probar, y los cambios en una parte del objeto de datos no afectan a los componentes que no dependen de esa parte.
Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones
El Principio de Inversión de Dependencias (DIP) es fundamental para construir sistemas flexibles y mantenibles. En esencia, propone que las dependencias deben dirigirse hacia abstracciones (interfaces o clases abstractas), en lugar de hacia implementaciones concretas. Esto fomenta el desacoplamiento y facilita la extensibilidad.
El DIP aboga por una estructura donde los módulos de alto nivel (lógica de negocio compleja) y los módulos de bajo nivel (detalles de implementación como bases de datos o servicios externos) dependan de abstracciones. Esto "invierte" la dependencia tradicional, donde los módulos de alto nivel dependerían directamente de los de bajo nivel. Al depender de interfaces, los componentes se vuelven intercambiables y el sistema es más robusto ante cambios.
En React, el DIP se manifiesta al usar patrones como la inyección de dependencias a través de Hooks personalizados o Context API. En lugar de que un componente dependa directamente de una implementación de servicio específica, puede depender de una interfaz (un Hook) que le proporciona la funcionalidad. Por ejemplo, un useAuth Hook que oculta la implementación de un proveedor de autenticación subyacente.
Para aplicaciones backend en Node.js, la inyección de dependencias es clave. En lugar de que un servicio de alto nivel instancie directamente un repositorio de base de datos (módulo de bajo nivel), el repositorio se "inyecta" como una dependencia a través del constructor o una función. Esto permite cambiar fácilmente la implementación del repositorio (ej., de MongoDB a PostgreSQL) sin modificar el servicio de alto nivel, siempre que ambos implementen la misma interfaz o contrato.
Beneficios clave: Mayor flexibilidad, facilidad para realizar pruebas unitarias (mocking de dependencias) y una arquitectura que escala mejor y es más adaptable a futuros cambios.
Componente React que depende directamente de implementación específica.
// services/UserService.js (Implementación Concreta)
export const UserService = {
getUser: (id) => {
// Simula una llamada API real a un endpoint
return new Promise(resolve => setTimeout(() => resolve({
id,
name: 'Usuario Concreto',
email: 'concreto@example.com'
}), 200));
}
};
// components/UserProfileDisplay.js (VIOLA DIP)
import React, { useEffect, useState } from 'react';
import { UserService } from '../services/UserService'; // <- Dependencia directa a una implementación concreta
function UserProfileDisplay({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// El componente sabe exactamente cómo obtener los datos
// y de dónde provienen. Es difícil de cambiar o testear.
UserService.getUser(userId).then(data => {
setUser(data);
});
}, [userId]);
if (!user) return <p>Cargando usuario...</p>;
return (
<div>
<h3>Perfil de Usuario (Directo)</h3>
<p>Nombre: {user.name}</p>
<p>Email: {user.email}</p>
</div>
);
}
// Uso: <UserProfileDisplay userId="1" />
Uso de hooks personalizados y abstracciones.
// interfaces/IUserService.js (Abstracción/Contrato)
// Define lo que un servicio de usuario DEBE hacer, sin importar CÓMO.
// En TypeScript sería una 'interface'. Aquí es implícito.
/*
interface IUserService {
getUser(id: string): Promise<User>;
}
*/
// services/ApiUserService.js (Implementación Concreta A)
export const ApiUserService = {
getUser: (id) => {
return new Promise(resolve => setTimeout(() => resolve({
id,
name: 'Usuario API',
email: 'api@example.com'
}), 200));
}
};
// services/MockUserService.js (Implementación Concreta B, para testing/desarrollo)
export const MockUserService = {
getUser: (id) => {
return new Promise(resolve => setTimeout(() => resolve({
id,
name: 'Usuario Mock',
email: 'mock@example.com'
}), 100));
}
};
// hooks/useUser.js (Hook Personalizado que depende de una Abstracción)
import { useEffect, useState } from 'react';
// El hook recibe el "servicio" como argumento (Inyección de Dependencias)
export const useUser = (userId, userService) => { // <- Depende de 'userService' (abstracción)
const [user, setUser] = useState(null);
useEffect(() => {
if (userService && userId) {
// El hook usa el servicio inyectado, sin saber su implementación
userService.getUser(userId).then(data => {
setUser(data);
});
}
}, [userId, userService]);
return user;
};
// components/UserProfileDisplay.js (SIGUE DIP)
import React from 'react';
import { useUser } from '../hooks/useUser';
import { ApiUserService } from '../services/ApiUserService'; // <- Se inyecta aquí, no dentro del componente
function UserProfileDisplay({ userId }) {
const user = useUser(userId, ApiUserService); // <- Inyección del servicio
if (!user) return <p>Cargando usuario...</p>;
return (
<div>
<h3>Perfil de Usuario (Con DIP)</h3>
<p>Nombre: {user.name}</p>
<p>Email: {user.email}</p>
</div>
);
}
// Uso: <UserProfileDisplay userId="1" />
// Para Testing: <UserProfileDisplay userId="1" userService={MockUserService} />
Este ejemplo demuestra cómo al depender de una abstracción (la "interfaz" implícita del userService que espera useUser), el componente de React y el hook no necesitan saber si están hablando con una API real o con un servicio de datos simulado. Esto facilita el testing, la refactorización y el intercambio de implementaciones, llevando a un sistema mucho más desacoplado y flexible.
Los principios SOLID son una base fundamental para construir sistemas de software robustos, flexibles y mantenibles. Al aplicarlos, transformamos la forma en que concebimos y desarrollamos nuestras aplicaciones.
Cada módulo o clase debe tener una sola razón para cambiar, lo que conduce a un código más claro y fácil de mantener.
Las entidades de software deben estar abiertas a la extensión, pero cerradas a la modificación, permitiendo añadir nuevas funcionalidades sin alterar el código existente.
Los subtipos deben ser sustituibles por sus tipos base sin alterar la corrección del programa, asegurando la coherencia en las jerarquías de clases.
Los clientes no deben ser forzados a depender de interfaces que no utilizan, promoviendo interfaces más pequeñas y específicas.
Los módulos de alto nivel no deben depender de módulos de bajo nivel; ambos deben depender de abstracciones, desacoplando componentes y facilitando la modularidad.
La implementación de los principios SOLID no solo mejora la calidad del código, sino que también fomenta una cultura de diseño consciente, facilitando el trabajo en equipo y la evolución del software. ¡Adopta estos principios para construir software excepcional!
Las variables de entorno son un mecanismo esencial para gestionar la configuración de aplicaciones de manera dinámica y segura, adaptándose a distintos entornos sin modificar el código.
Son pares clave-valor almacenados fuera del código fuente de una aplicación, accesibles por el sistema operativo y, por extensión, por cualquier programa o script que se ejecute en él. Permiten que una aplicación se comporte de manera diferente en distintos entornos (desarrollo, pruebas, producción) sin necesidad de recompilar o alterar el código.
Evitan la codificación de información sensible (como claves de API o credenciales de bases de datos) directamente en el código fuente, reduciendo el riesgo de exposición.
Permiten que la misma base de código se ejecute en múltiples entornos (desarrollo, staging, producción) con diferentes configuraciones, facilitando la implementación continua.
Fomentan una clara distinción entre el código de la aplicación y sus ajustes operativos, mejorando la mantenibilidad y la organización del proyecto.
La configuración de una aplicación debe ser externa a su código para garantizar flexibilidad, seguridad y adaptabilidad a diferentes entornos sin necesidad de modificaciones o recompilaciones.
En Node.js, las variables de entorno son accesibles a través del objeto global process.env. Este objeto contiene todas las variables de entorno que están disponibles para el proceso de Node.js en el momento en que se inicia.
Para gestionar estas variables de manera sencilla en el desarrollo local, especialmente para cargar configuraciones desde un archivo .env, se utiliza comúnmente la librería dotenv. Esta librería permite separar la configuración de secretos del código, mejorando la seguridad y la portabilidad.
Valores como DB_HOST, DB_USER, DB_PASSWORD, y DB_NAME para conectar a bases de datos en distintos entornos.
Almacenar de forma segura credenciales sensibles como STRIPE_SECRET_KEY, AWS_ACCESS_KEY_ID o JWT_SECRET.
Definir el puerto de escucha de la aplicación (PORT) o URLs de servicios externos (API_URL) que varían según el entorno.
dotenv// 1. Instalar dotenv: npm install dotenv
// 2. Crear un archivo .env en la raíz de tu proyecto:
// DB_HOST=localhost
// API_KEY=tu_clave_secreta_aqui
// PORT=3000
// app.js
require('dotenv').config(); // Carga las variables del .env
const DB_HOST = process.env.DB_HOST || 'default_db_host';
const API_KEY = process.env.API_KEY;
const PORT = process.env.PORT || 8080;
console.log(`Conectando a la base de datos en: ${DB_HOST}`);
console.log(`Usando API Key: ${API_KEY ? '*****' : 'No definida'}`);
console.log(`Servidor escuchando en el puerto: ${PORT}`);
// Ejemplo de uso de una variable en una ruta Express
// const express = require('express');
// const app = express();
// app.listen(PORT, () => console.log(`App en http://localhost:${PORT}`));
.env: Añádelos a tu .gitignore para evitar que las credenciales y configuraciones sensibles se filtren al control de versiones.process.env.VARIABLE || 'valor_por_defecto') para que la aplicación funcione incluso si la variable no está definida..env.development, .env.production) según el entorno de despliegue.La configuración de una aplicación móvil multiplataforma debe ser externa a su código para garantizar flexibilidad, seguridad y adaptabilidad a diferentes entornos sin necesidad de modificaciones o recompilaciones.
En el desarrollo de aplicaciones móviles con React Native y Expo, la gestión de variables de entorno es crucial para adaptar la aplicación a distintos contextos (desarrollo, staging, producción) sin modificar el código base. Este es el método moderno recomendado por Expo.
Expo facilita esta tarea permitiendo la definición de variables de entorno que son accesibles directamente a través de process.env en el código de tu aplicación. Para que las variables de entorno sean accesibles en el frontend y se incluyan en el bundle de tu aplicación, deben comenzar con el prefijo EXPO_PUBLIC_.
Las variables con el prefijo EXPO_PUBLIC_ se inyectan en tu aplicación durante el proceso de build y están disponibles en el entorno de ejecución, lo que permite que tu aplicación lea variables específicas del entorno sin necesidad de configuraciones complejas en archivos como app.config.js o el uso de Constants.manifest.extra para este propósito.
Cambiar fácilmente la URL del backend entre entornos de desarrollo, pruebas y producción (por ejemplo, EXPO_PUBLIC_API_URL=https://dev.api.com vs. EXPO_PUBLIC_API_URL=https://prod.api.com).
Definir diferentes claves de API públicas para servicios de terceros (Google Maps, Stripe) o configuraciones específicas de analytics para cada entorno, siempre usando el prefijo EXPO_PUBLIC_.
Activar o desactivar funcionalidades específicas en función del entorno, permitiendo pruebas A/B o despliegues progresivos, controladas por variables de entorno prefijadas con EXPO_PUBLIC_.
// .env (en la raíz de tu proyecto, no debe ser versionado)
EXPO_PUBLIC_API_URL=https://dev.api.com
EXPO_PUBLIC_STRIPE_KEY=pk_test_YOUR_STRIPE_KEY
EXPO_PUBLIC_ENABLE_ANALYTICS=true
// En tu componente React Native (o cualquier archivo .js/.ts)
// No necesitas importar nada especial para acceder a process.env
const apiUrl = process.env.EXPO_PUBLIC_API_URL || 'https://default.api.com';
const stripeKey = process.env.EXPO_PUBLIC_STRIPE_KEY;
const enableAnalytics = process.env.EXPO_PUBLIC_ENABLE_ANALYTICS === 'true'; // Las variables son strings
console.log('API URL:', apiUrl);
console.log('Stripe Key:', stripeKey ? '*****' : 'No definida');
console.log('Analytics Habilitado:', enableAnalytics);
// Ejemplo de uso
const fetchData = async () => {
try {
const response = await fetch(`${apiUrl}/data`);
const data = await response.json();
// ...
} catch (error) {
console.error('Error fetching data:', error);
}
};
Durante el desarrollo local, puedes definir variables de entorno en un archivo .env que luego son cargadas automáticamente por Expo. Para los builds de producción o staging, estas variables (con el prefijo EXPO_PUBLIC_) deben ser inyectadas durante el proceso de build de Expo (por ejemplo, a través de variables de entorno del CI/CD), asegurando que se incluyan en el bundle final de la aplicación.
EXPO_PUBLIC_ se usan para ocultar valores cambiantes, el código del frontend (incluso compilado) es accesible por los usuarios. Solo expón claves de API públicas o datos no sensibles. Para secretos sensibles (ej. claves de API privadas), usa un backend seguro.EXPO_PUBLIC_: Asegúrate de que todas las variables de entorno destinadas al uso en el frontend de tu aplicación Expo comiencen con EXPO_PUBLIC_.process.env.EXPO_PUBLIC_VARIABLE || 'valor_por_defecto') si una variable no es crítica, para asegurar que la aplicación funcione incluso si la variable no está definida en el entorno.EXPO_PUBLIC_) deben ser inyectadas de forma segura a través de tu sistema de integración continua/despliegue continuo (CI/CD) antes de que Expo compile tu aplicación..env para desarrollo local: Utiliza un archivo .env (y añádelo a .gitignore) para gestionar las variables de entorno en tu máquina local. Expo lo reconocerá automáticamente y hará que las variables con EXPO_PUBLIC_ sean accesibles.La gestión de variables de entorno es fundamental para adaptar las aplicaciones a diferentes entornos (desarrollo, pruebas, producción) sin alterar el código base. A continuación, se presenta una tabla comparativa de cómo se manejan las variables de entorno en distintas tecnologías, basada en la información disponible.
El enfoque elegido para la gestión de variables de entorno debe alinearse con la arquitectura de la aplicación, el entorno de despliegue y los requisitos de seguridad.
app.config.js y se acceden a través de expo-constants, adaptándose a diferentes builds.Independientemente de la tecnología, existen prácticas clave para una gestión segura y eficiente de las variables de entorno:
.envAñádelos a tu .gitignore para evitar que las credenciales y configuraciones sensibles se filtren al control de versiones.
Valida la existencia y el formato de las variables de entorno críticas al inicio de la aplicación para evitar errores en tiempo de ejecución.
Utiliza herramientas o scripts para cargar diferentes conjuntos de variables (ej. .env.development, .env.production) según el entorno de despliegue.
El código del frontend es accesible por los usuarios. Solo expón claves públicas de API o datos no sensibles. Para secretos sensibles, usa un backend seguro.
En entornos de producción, las variables de entorno deben ser inyectadas de forma segura a través de tu sistema de integración continua/despliegue continuo (CI/CD) antes de la compilación.
La correcta gestión de las variables de entorno es un pilar fundamental para el desarrollo y despliegue de aplicaciones modernas. Permite adaptar el comportamiento de la aplicación a diferentes entornos sin necesidad de modificar el código fuente, lo que se traduce en una mayor agilidad y menos errores, garantizando la seguridad y la flexibilidad necesaria en el ciclo de vida del software.
Ajusta tu aplicación a diversos entornos (desarrollo, pruebas, producción) de manera dinámica y sin re-despliegue de código.
Protege información sensible, como credenciales y claves API, manteniéndolas fuera del control de versiones y accesibles solo para entornos autorizados.
Simplifica la configuración y reduce la complejidad, haciendo el código más limpio, modular y fácil de gestionar a largo plazo.
Facilita el despliegue en infraestructuras distribuidas y entornos de CI/CD, adaptándose sin problemas a las demandas de crecimiento y nuevas configuraciones.
Adoptar estas prácticas no solo mejora la seguridad y la eficiencia de tus proyectos, sino que también sienta las bases para arquitecturas más robustas y preparadas para el futuro, permitiendo una evolución constante de tus sistemas.
Te invitamos a aplicar estos principios en tus próximos desarrollos, construyendo aplicaciones más seguras, adaptables y fáciles de mantener en cualquier escenario.
an A
Una guía completa para escribir código mantenible, escalable y profesional en el ecosistema JavaScript moderno.