"El código se lee mucho más de lo que se escribe. Optimizar para la lectura es optimizar para el éxito del proyecto."


"Cada módulo o componente debe tener una única razón para cambiar"
SurahCard solo muestra datos, no los obtiene de la API.// 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 software debe estar abierto para extensión, pero cerrado para modificación."
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.// 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.
"Los objetos de una clase derivada deben poder reemplazar objetos de la clase base sin alterar el funcionamiento del programa."
B es una subclase de A, entonces un objeto de B puede ser usado donde se espera un objeto de A.<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.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."Los clientes no deberían verse obligados a depender de interfaces que no utilizan."
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 }./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.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={[]}
// />
{/* 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} />
Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones
useAuth Hook que oculta la implementación de un proveedor de autenticación subyacente. // 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" />
// 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} />
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.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.
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..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.DB_HOST, DB_USER, DB_PASSWORD, y DB_NAME para conectar a bases de datos en distintos entornos.STRIPE_SECRET_KEY, AWS_ACCESS_KEY_ID o JWT_SECRET.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.
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_.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.EXPO_PUBLIC_API_URL=https://dev.api.com vs. EXPO_PUBLIC_API_URL=https://prod.api.com).EXPO_PUBLIC_.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);
}
};
.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.app.config.js y se acceden a través de expo-constants, adaptándose a diferentes builds..env.gitignore para evitar que las credenciales y configuraciones sensibles se filtren al control de versiones..env.development, .env.production) según el entorno de despliegue.