Cómo empezar un proyecto web sin morir en el intento
Arrancar un proyecto web desde cero tiene un momento peligroso: los primeros 30 minutos. Es cuando se toman decisiones apresuradas que persiguen el proyecto durante meses. Estructura de carpetas improvisada, variables hardcodeadas por todas partes, commits de “prueba inicial” que nunca se limpian. Deuda técnica por todas partes…
Esta guía va sobre esos primeros pasos. Los que nadie te cuenta porque parecen obvios pero claramente no lo son.
1. Antes de escribir una línea de código: planificar
El mayor error al empezar es abrir el editor antes de tener claro qué se va a construir. No hace falta un documento de 50 páginas — hace falta un sitio donde volcar las ideas y organizarlas.
Trello (o cualquier tablero Kanban)
Un tablero Trello con estas columnas es suficiente para cualquier proyecto personal o de equipo pequeño:
- Backlog — todo lo que quieres hacer, sin orden
- To Do — lo que toca en este sprint o semana
- Doing — en lo que estás trabajando ahora mismo (máximo 2-3 tarjetas)
- Done — terminado y funcionando
- Parking Lot — ideas interesantes que no puedes abordar ahora pero no quieres perder
La columna Parking Lot es la más infravalorada. Evita que buenas ideas contaminen el foco actual. Cuando se te ocurra “sería genial añadir autenticación con Google”, en vez de ponerte a investigar OAuth en mitad de maquetar el home, va al Parking Lot. Vuelves cuando toca.
Cada tarjeta debería tener: descripción clara, criterio de aceptación (“está hecho cuando…”) y etiquetas por tipo (feature, bug, mejora, investigación).
2. Repositorio desde el primer commit
No “cuando tenga algo que enseñar”. Desde el primer archivo.
git init
git add .
git commit -m "init: estructura inicial del proyecto"
Y súbelo a GitHub/GitLab/Bitbucket inmediatamente. Aunque sea privado. Los beneficios son:
- Historial desde el día uno
- Posibilidad de volver a cualquier punto
- Colaboración desde el primer momento si la necesitas
- El
.gitignorecorrecto desde el inicio (no después de haber subidonode_modulespor error)
.gitignore desde el principio
node_modules/
dist/
.env
.env.local
.DS_Store
*.log
El archivo .env nunca debe ir al repositorio. Nunca. Ni en proyectos personales. El hábito de no subirlo se forma desde el primer proyecto.
3. Variables de entorno y configuración
Antes de tocar la base de datos, las APIs o cualquier servicio externo, hay que hacer un inventario de todo lo que el proyecto va a necesitar. No es un paso que se pueda dejar para más tarde: si descubres a mitad de una sesión de código que necesitas una API key de Stripe, pierdes el contexto, te dispersas y acabas con credenciales a medias por todas partes.
Paso 0: inventario de servicios antes de programar
Cuando tengas claro qué va a hacer el proyecto, hazte estas preguntas:
Base de datos
- ¿MongoDB Atlas? → necesitas la
MONGO_URIcon usuario, contraseña y nombre del cluster - ¿PostgreSQL / MySQL? →
DB_HOST,DB_PORT,DB_USER,DB_PASSWORD,DB_NAME - ¿Supabase o Firebase? → project URL + anon/service key
Autenticación
- ¿JWT propio? → genera ya un
JWT_SECRETlargo y aleatorio (openssl rand -base64 64) - ¿OAuth (Google, GitHub…)? →
OAUTH_CLIENT_ID+OAUTH_CLIENT_SECRETde la consola del proveedor - ¿Auth0 / Clerk / Supabase Auth? → domain + API key
Servicios de terceros
- Pagos (Stripe, PayPal) →
STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WEBHOOK_SECRET - Email (Resend, SendGrid, Nodemailer) →
RESEND_API_KEYo credenciales SMTP - Almacenamiento (Cloudinary, AWS S3) →
CLOUDINARY_URLoAWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY - Mapas, analytics, IA → cada servicio tiene su key
URLs y entorno
PUBLIC_SITE_URLpara emails transaccionales y redirects OAuthNODE_ENVpara distinguir desarrollo de producción
Con este inventario hecho, crea el .env y el .env.example de golpe, antes de escribir una sola línea de lógica. Así no tendrás que parar a mitad para buscar credenciales ni te arriesgas a hardcodear un valor “provisionalmente”.
# Genera un JWT_SECRET seguro desde la terminal
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# o con openssl:
openssl rand -base64 64
El archivo .env
# .env (NO subir a git)
MONGO_URI=mongodb+srv://usuario:password@cluster.mongodb.net/miapp
JWT_SECRET=una_clave_muy_larga_y_aleatoria
API_KEY_STRIPE=sk_test_...
PUBLIC_SITE_URL=http://localhost:4321
El archivo .env.example
Este sí va al repositorio. Es la plantilla que cualquier desarrollador (o el tú del futuro) necesita para poner el proyecto en marcha:
# .env.example (SÍ subir a git)
MONGO_URI=
JWT_SECRET=
API_KEY_STRIPE=
PUBLIC_SITE_URL=
Cargarlo en el código
En Node.js — necesitas instalar el paquete dotenv:
npm install dotenv
// Al inicio de tu archivo principal (index.js, server.js...)
import 'dotenv/config';
const uri = process.env.MONGO_URI;
const secret = process.env.JWT_SECRET;
Con import 'dotenv/config' ya está. Lee el .env automáticamente y mete todo en process.env.
En Vite / React — no necesitas instalar nada. Vite lee el .env de forma nativa, pero con una regla importante: solo expone al navegador las variables que empiezan por VITE_:
# .env
VITE_API_URL=https://api.miapp.com
VITE_PUBLIC_KEY=pk_live_...
DB_PASSWORD=secreto # ← esta NO llega al cliente, solo al servidor
// En cualquier componente React
const apiUrl = import.meta.env.VITE_API_URL;
const pubKey = import.meta.env.VITE_PUBLIC_KEY;
En Astro — también nativo, sin instalación:
// Las variables que empiezan por PUBLIC_ son accesibles en el cliente
const apiUrl = import.meta.env.PUBLIC_SITE_URL;
const secret = import.meta.env.JWT_SECRET; // solo server-side
Centraliza el acceso a las variables en un único archivo de configuración. Si mañana cambia el nombre de una variable, lo cambias en un sitio, no en veinte.
// src/config.ts
export const config = {
mongoUri: import.meta.env.MONGO_URI,
jwtSecret: import.meta.env.JWT_SECRET,
siteUrl: import.meta.env.PUBLIC_SITE_URL,
};
4. Estructura de carpetas
No existe la estructura perfecta universal, pero sí existen estructuras que se pagan más tarde. La regla es: cualquier persona del equipo (o tú en 6 meses) debería encontrar cualquier archivo en menos de 30 segundos.
Una estructura sólida para un proyecto web:
src/
components/ # componentes reutilizables
layouts/ # plantillas de página
pages/ # rutas / vistas
styles/ # CSS global, variables, reset
utils/ # funciones auxiliares puras
lib/ # integraciones externas (db, auth...)
types/ # tipos TypeScript compartidos
content/ # contenido estático o colecciones MDX
public/ # assets estáticos (imágenes, fuentes, favicon)
.env
.env.example
.gitignore
Agrupa por tipo de responsabilidad, no por tecnología. Un componente de un formulario de contacto tiene su HTML, CSS y lógica juntos — no el HTML en /templates, el CSS en /styles/forms y el JS en /scripts/forms.
5. Reset CSS: el punto de partida limpio
Cada navegador tiene sus propios estilos por defecto. Los botones, formularios, listas y márgenes se ven diferente en Chrome, Firefox y Safari. El reset CSS borra esas diferencias para empezar desde cero.
Un reset moderno y minimalista que funciona bien:
/* styles/reset.css */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
-webkit-text-size-adjust: 100%;
}
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
ul, ol {
list-style: none;
}
a {
text-decoration: none;
color: inherit;
}
Lo importante: se importa antes que cualquier otro CSS. Todo lo que viene después construye sobre este estado limpio predecible.
6. Variables CSS: el sistema de diseño mínimo
Las variables CSS (custom properties) son el lugar donde vive el “lenguaje visual” del proyecto: colores, tamaños, radios, sombras. Definirlas al principio ahorra horas de buscar y reemplazar después.
/* styles/variables.css */
:root {
/* Colores */
--color-primary: #3d5a80;
--color-secondary: #e0fbfc;
--color-accent: #ee6c4d;
--color-text: #1a1a2e;
--color-text-muted: #6b7280;
--color-bg: #ffffff;
--color-bg-alt: #f8f9fa;
--color-border: #e5e7eb;
/* Tipografía */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'Fira Code', 'Courier New', monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
/* Espaciado */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
/* Layout */
--content-width: 800px;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
/* Sombras */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
--shadow-md: 0 4px 12px rgba(0,0,0,0.15);
}
Cuando el cliente pide “cambiar el azul por verde”, cambias una línea. Sin variables, buscas 47 instancias de #3d5a80 por todo el proyecto.
7. Tipografías: Google Fonts sin repetirte
La forma correcta de usar Google Fonts no es copiar el <link> en cada HTML que crees. Es importarlo una sola vez en el CSS global.
Opción 1: desde el CSS global (recomendada)
/* Al inicio de styles/global.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap');
Una sola línea. Se carga en toda la web. Si cambias de fuente, lo cambias en un sitio.
Opción 2: <link> en el layout base
Si tu framework usa un layout o plantilla base que se aplica a todas las páginas, ponlo ahí:
<!-- layouts/BaseLayout.astro o _document.tsx, etc. -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet" />
El preconnect le dice al navegador que empiece a conectar con los servidores de Google Fonts antes de que se evalúe el <link> de la fuente — mejora el tiempo de carga.
Después: úsala desde la variable
body {
font-family: var(--font-sans); /* no hardcodear el nombre aquí */
}
8. El CSS global: el orden importa
Cuando tengas varios archivos CSS, el orden de importación define qué puede sobreescribir qué:
/* styles/global.css — el único que importas en tu layout */
/* 1. Fuentes externas */
@import url('https://fonts.googleapis.com/...');
/* 2. Variables */
@import './variables.css';
/* 3. Reset */
@import './reset.css';
/* 4. Estilos base */
@import './base.css';
/* 5. Utilidades */
@import './utilities.css';
Reset antes que base. Variables antes que todo. Las utilidades al final para poder sobreescribir cuando sea necesario.
9. Preparación del entorno de desarrollo
Node.js y el gestor de paquetes
Fija la versión de Node.js en un archivo .nvmrc o engines en package.json:
{
"engines": {
"node": ">=20.0.0"
}
}
Ahorra el clásico “en mi máquina funciona” cuando un compañero tiene Node 16 y tú tienes Node 22.
Linting y formato
Configúralos el día uno, no cuando el código ya sea un desastre:
npm install -D eslint prettier
Un .prettierrc mínimo:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
Con esto, todo el equipo (o el tú del futuro) escribe con el mismo estilo. Sin debates sobre tabs vs espacios.
El README
Escríbelo desde el primer día. No hace falta que sea un manual — solo lo imprescindible:
## Requisitos
- Node.js 20+
- Variables de entorno (ver .env.example)
## Instalación
npm install
## Desarrollo
npm run dev
## Build
npm run build
10. Subida a producción
Un proyecto que solo vive en localhost no es un proyecto, es un borrador. Conectar el repositorio a una plataforma de despliegue cuesta menos de 5 minutos y, a partir de ahí, cada git push actualiza la web automáticamente.
Las opciones más habituales
| Plataforma | Ideal para | Plan gratuito |
|---|---|---|
| Vercel | Next.js, React, cualquier frontend | Sí |
| Netlify | Astro, sitios estáticos, JAMstack | Sí |
| Cloudflare Pages | Sitios estáticos, máximo rendimiento global | Sí |
| Render | Node.js, APIs, backends con base de datos | Sí (con limitaciones) |
El flujo básico (igual en todas)
- Sube el repositorio a GitHub / GitLab
- Crea una cuenta en la plataforma y conecta el repositorio
- Configura el comando de build y la carpeta de salida
- Añade las variables de entorno en el panel (las del
.env) - Despliega — la URL está lista en segundos
Configuración típica
Astro:
Build command: npm run build
Output directory: dist
Next.js en Vercel: Sin configuración. Vercel detecta Next.js automáticamente y lo configura solo.
Node.js / Express en Render:
Build command: npm install
Start command: node index.js
Variables de entorno en producción
Este es el paso que más gente olvida. El .env no está en el repositorio, así que la plataforma no lo ve. Hay que añadir cada variable manualmente en el panel de configuración:
- Vercel: Settings → Environment Variables
- Netlify: Site configuration → Environment variables
- Cloudflare Pages: Settings → Environment variables
- Render: Environment → Environment Variables
Si una variable falta, el build puede pasar pero la app falla en producción con errores crípticos. El .env.example es la lista de comprobación — cópialo y rellena los valores reales.
Dominios personalizados
Todas estas plataformas asignan una URL por defecto (tu-proyecto.vercel.app, tu-proyecto.netlify.app…). Para usar tu propio dominio:
- Añade el dominio en la plataforma
- En tu proveedor de DNS, apunta el registro
CNAMEoAa la dirección que te indica la plataforma - El certificado SSL/TLS se genera automáticamente (HTTPS gratis)
El checklist del día uno
Antes de ponerte a programar de verdad, estas son las casillas que deberían estar marcadas:
- Tablero Kanban creado con Backlog, To Do, Doing, Done y Parking Lot
- Repositorio inicializado y subido a remoto
-
.gitignorecorrecto (node_modules, dist, .env) - Inventario de servicios hecho (BBDD, auth, APIs, email, storage…)
- Credenciales obtenidas y
.env+.env.examplecreados - Estructura de carpetas decidida
- Reset CSS importado
- Variables CSS con colores, tipografía y espaciado base
- Tipografías importadas en el CSS global o layout base
- Linting y formato configurados
- README con instrucciones de instalación
- Repositorio conectado a una plataforma de despliegue
- Variables de entorno configuradas en producción
Esto nos llevará una hora como mucho. A cambio: un proyecto que escala bien, que cualquiera puede clonar y arrancar, y en el que tú mismo no te perderás cuando vuelvas tras dos semanas sin tocarlo.
La preparación no retrasa el proyecto — lo acelera.