50 Preguntas de Entrevista Técnica para Desarrolladores Full Stack (con Respuestas)
50 Preguntas de Entrevista Técnica para Desarrolladores Full Stack (con Respuestas)
Las entrevistas técnicas para posiciones Full Stack pueden ser intimidantes. Los reclutadores evalúan tanto tus conocimientos de frontend como backend, además de tu capacidad para integrar ambos mundos. Esta guía contiene las 50 preguntas más frecuentes en entrevistas Full Stack, organizadas por categoría, con respuestas detalladas que te ayudarán a prepararte con confianza.
JavaScript y Fundamentos de Programación
1. ¿Cuál es la diferencia entre var, let y const?
Respuesta:
var: Tiene scope de función, puede ser redeclarada y reasignada. Sufre de hoisting (se eleva al inicio del scope).let: Tiene scope de bloque, no puede ser redeclarada pero sí reasignada. También sufre hoisting pero con "temporal dead zone".const: Tiene scope de bloque, no puede ser redeclarada ni reasignada (aunque sus propiedades sí pueden modificarse si es un objeto). Requiere inicialización al momento de declararse.
Ejemplo:
javascript
var x = 1;
var x = 2; // ✓ Funciona
let y = 1;
let y = 2; // ✗ Error: y ya fue declarada
const z = 1;
z = 2; // ✗ Error: no se puede reasignar
const obj = { name: 'Ana' };
obj.name = 'Carlos'; // ✓ Funciona (modificas la propiedad, no la referencia)
2. ¿Qué es el hoisting en JavaScript?
Respuesta:
Hoisting es el comportamiento de JavaScript donde las declaraciones de variables y funciones se "mueven" al inicio de su scope antes de la ejecución del código. Solo se elevan las declaraciones, no las inicializaciones.
javascript
console.log(nombre); // undefined (no error)
var nombre = 'Juan';
console.log(apellido); // ReferenceError
let apellido = 'Pérez';
saludar(); // "Hola" - funciona porque las funciones declaradas se elevan completamente
function saludar() {
console.log('Hola');
}
3. Explica los conceptos de closures en JavaScript
Respuesta:
Un closure es una función que tiene acceso a variables de su scope exterior, incluso después de que la función exterior haya terminado de ejecutarse. Los closures "recuerdan" el entorno en el que fueron creados.
javascript
function crearContador() {
let contador = 0;
return function() {
contador++;
return contador;
};
}
const miContador = crearContador();
console.log(miContador()); // 1
console.log(miContador()); // 2
console.log(miContador()); // 3
Usos prácticos: Encapsulación de datos, factory functions, callbacks, event handlers.
4. ¿Cuál es la diferencia entre == y ===?
Respuesta:
==(igualdad débil): Compara valores después de hacer coerción de tipos (conversión automática).===(igualdad estricta): Compara valores Y tipos sin hacer conversión.
javascript
5 == '5' // true (convierte '5' a número) 5 === '5' // false (tipos diferentes) null == undefined // true null === undefined // false 0 == false // true 0 === false // false
Mejor práctica: Usa siempre === a menos que específicamente necesites coerción de tipos.
5. ¿Qué es el Event Loop en JavaScript?
Respuesta:
El Event Loop es el mecanismo que permite a JavaScript ejecutar código asíncrono a pesar de ser single-threaded. Funciona así:
- Call Stack: Ejecuta código síncronamente
- Web APIs: Manejan operaciones asíncronas (setTimeout, fetch, etc.)
- Callback Queue: Cola de callbacks listos para ejecutarse
- Event Loop: Revisa si el Call Stack está vacío y mueve callbacks desde la Queue al Stack
javascript
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// Resultado: 1, 4, 3, 2
// (Las promesas tienen prioridad sobre setTimeout en la Microtask Queue)
6. Explica this en JavaScript
Respuesta:
this se refiere al objeto que está ejecutando la función actual. Su valor depende del contexto de ejecución:
javascript
// En objeto
const persona = {
nombre: 'Ana',
saludar() {
console.log(this.nombre); // 'Ana'
}
};
// Arrow functions NO tienen su propio this
const persona2 = {
nombre: 'Carlos',
saludar: () => {
console.log(this.nombre); // undefined (this del scope padre)
}
};
// Con bind/call/apply puedes cambiar el this
function mostrarNombre() {
console.log(this.nombre);
}
const usuario = { nombre: 'María' };
mostrarNombre.call(usuario); // 'María'
7. ¿Qué son las Promises y cómo funcionan?
Respuesta:
Una Promise es un objeto que representa el resultado eventual (éxito o fallo) de una operación asíncrona. Tiene tres estados:
- Pending: Estado inicial
- Fulfilled: Operación completada exitosamente
- Rejected: Operación falló
javascript
const miPromesa = new Promise((resolve, reject) => {
setTimeout(() => {
const exito = true;
if (exito) {
resolve('Datos obtenidos');
} else {
reject('Error al obtener datos');
}
}, 1000);
});
miPromesa
.then(resultado => console.log(resultado))
.catch(error => console.error(error))
.finally(() => console.log('Operación finalizada'));
8. ¿Cuál es la diferencia entre .map(), .forEach(), .filter() y .reduce()?
Respuesta:
javascript
const numeros = [1, 2, 3, 4, 5]; // forEach: Itera sin retornar nada numeros.forEach(n => console.log(n)); // undefined // map: Transforma cada elemento y retorna nuevo array const dobles = numeros.map(n => n * 2); // [2, 4, 6, 8, 10] // filter: Retorna nuevo array con elementos que cumplen condición const pares = numeros.filter(n => n % 2 === 0); // [2, 4] // reduce: Reduce array a un solo valor const suma = numeros.reduce((acc, n) => acc + n, 0); // 15
9. Explica async/await
Respuesta:
async/await es sintaxis moderna para trabajar con Promises de forma más legible, parecida a código síncrono.
javascript
// Con Promises
function obtenerUsuario() {
return fetch('/api/user')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
}
// Con async/await
async function obtenerUsuario() {
try {
const res = await fetch('/api/user');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
Reglas:
- Solo puedes usar
awaitdentro de funcionesasync asyncsiempre retorna una Promise- Usa
try/catchpara manejar errores
10. ¿Qué es el spread operator y rest parameters?
Respuesta:
Spread operator (...): Expande elementos de un iterable
javascript
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
Rest parameters: Agrupa argumentos en un array
javascript
function sumar(...numeros) {
return numeros.reduce((acc, n) => acc + n, 0);
}
sumar(1, 2, 3, 4); // 10
React y Frontend
11. ¿Qué son los componentes en React?
Respuesta:
Los componentes son piezas reutilizables e independientes de UI. Hay dos tipos:
Funcionales (recomendados):
javascript
function Saludo({ nombre }) {
return <h1>Hola, {nombre}</h1>;
}
De clase (legacy):
javascript
class Saludo extends React.Component {
render() {
return <h1>Hola, {this.props.nombre}</h1>;
}
}
Características:
- Reciben props (datos de entrada)
- Retornan JSX (elementos React)
- Pueden manejar estado local
- Deben tener nombres en PascalCase
12. ¿Qué es el Virtual DOM?
Respuesta:
El Virtual DOM es una representación en memoria del DOM real. React usa un algoritmo de "reconciliación" para comparar versiones:
- Se crea un árbol virtual del componente
- Cuando hay cambios, se crea un nuevo árbol virtual
- React compara ambos árboles (diffing)
- Solo actualiza en el DOM real lo que cambió
Ventajas:
- Mejor performance (batch updates)
- Menos manipulaciones directas del DOM
- Permite rendering declarativo
13. Explica useState y useEffect
Respuesta:
useState: Hook para agregar estado local a componentes funcionales
javascript
function Contador() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicks: {count}
</button>
);
}
useEffect: Hook para efectos secundarios (API calls, subscripciones, DOM)
javascript
function Usuario({ id }) {
const [usuario, setUsuario] = useState(null);
useEffect(() => {
// Se ejecuta después del render
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => setUsuario(data));
// Cleanup function (opcional)
return () => {
console.log('Componente desmontado');
};
}, [id]); // Array de dependencias
return <div>{usuario?.nombre}</div>;
}
14. ¿Cuál es la diferencia entre props y state?
Respuesta:
PropsStateDatos que recibe el componenteDatos internos del componenteInmutables desde el componente hijoMutable con setState/useStateVienen del componente padreSe inicializa en el componenteUsadas para comunicación descendenteUsado para interactividad local
javascript
// Props
function Hijo({ mensaje }) {
// mensaje es inmutable aquí
return <p>{mensaje}</p>;
}
function Padre() {
return <Hijo mensaje="Hola desde padre" />;
}
// State
function Contador() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
15. ¿Qué es el prop drilling y cómo lo evitas?
Respuesta:
Prop drilling es pasar props a través de múltiples niveles de componentes que no las usan, solo para llegar al componente final.
Problema:
javascript
function App() {
const [user, setUser] = useState({ name: 'Ana' });
return <Nivel1 user={user} />;
}
function Nivel1({ user }) {
return <Nivel2 user={user} />; // solo pasa la prop
}
function Nivel2({ user }) {
return <Nivel3 user={user} />; // solo pasa la prop
}
function Nivel3({ user }) {
return <h1>{user.name}</h1>; // finalmente la usa
}
Soluciones:
- Context API:
javascript
const UserContext = React.createContext();
function App() {
const [user, setUser] = useState({ name: 'Ana' });
return (
<UserContext.Provider value={user}>
<Nivel1 />
</UserContext.Provider>
);
}
function Nivel3() {
const user = useContext(UserContext);
return <h1>{user.name}</h1>;
}
- State management (Redux, Zustand)
- Component composition
16. ¿Qué son los React Hooks más comunes?
Respuesta:
useState: Estado local
javascript
const [state, setState] = useState(initialValue);
useEffect: Efectos secundarios
javascript
useEffect(() => { /* efecto */ }, [dependencies]);
useContext: Consumir contexto
javascript
const value = useContext(MyContext);
useRef: Referencia mutable que persiste entre renders
javascript
const inputRef = useRef(null);
<input ref={inputRef} />
useMemo: Memoiza valores computados
javascript
const valorCostoso = useMemo(() => calcularAlgo(data), [data]);
useCallback: Memoiza funciones
javascript
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
useReducer: Estado complejo con lógica tipo Redux
javascript
const [state, dispatch] = useReducer(reducer, initialState);
17. Explica el ciclo de vida de un componente React
Respuesta:
En componentes funcionales con hooks:
javascript
function Componente() {
// Montaje: Se ejecuta una vez
useEffect(() => {
console.log('Componente montado');
// Desmontaje: Cleanup
return () => {
console.log('Componente desmontado');
};
}, []); // Array vacío = solo al montar
// Actualización: Cada vez que cambia 'count'
useEffect(() => {
console.log('Count cambió');
}, [count]);
// Cada render
useEffect(() => {
console.log('Componente renderizado');
}); // Sin array de dependencias
}
Equivalencias con clases:
useEffect(() => {}, [])→componentDidMountuseEffect(() => {})→componentDidUpdateuseEffect(() => { return () => {} }, [])→componentWillUnmount
18. ¿Qué es el controlled vs uncontrolled components?
Respuesta:
Controlled Component: React controla el valor del input
javascript
function FormControlado() {
const [valor, setValor] = useState('');
return (
<input
value={valor}
onChange={(e) => setValor(e.target.value)}
/>
);
}
Uncontrolled Component: DOM controla el valor
javascript
function FormNoControlado() {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return <input ref={inputRef} />;
}
Recomendación: Usa controlled components para formularios con validación en tiempo real.
19. ¿Qué es Redux y cuándo lo usarías?
Respuesta:
Redux es una librería de state management basada en tres principios:
- Single source of truth: Un solo store
- State is read-only: Solo se modifica con actions
- Changes with pure functions: Reducers puros
javascript
// Action
const incrementar = () => ({ type: 'INCREMENT' });
// Reducer
function contadorReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
}
// Store
const store = createStore(contadorReducer);
// Dispatch
store.dispatch(incrementar());
Úsalo cuando:
- Múltiples componentes necesitan el mismo estado
- Estado se actualiza frecuentemente
- Lógica de actualización es compleja
- App mediana/grande con muchos estados compartidos
No lo uses si: Tu app es pequeña, Context API es suficiente.
20. ¿Qué es el key prop en listas de React?
Respuesta:
key es un atributo especial que ayuda a React a identificar qué elementos cambiaron, se agregaron o eliminaron en una lista.
Malo:
javascript
items.map((item, index) => (
<li key={index}>{item.nombre}</li>
// ❌ Usar index puede causar bugs si el orden cambia
));
Bueno:
javascript
items.map(item => (
<li key={item.id}>{item.nombre}</li>
// ✓ Usa un ID único y estable
));
Por qué importa:
- Sin keys, React re-renderiza toda la lista
- Con keys incorrectas, el estado puede quedar en el elemento equivocado
- Keys deben ser únicas entre hermanos, no globalmente
Node.js y Backend
21. ¿Qué es Node.js y cómo funciona?
Respuesta:
Node.js es un runtime de JavaScript basado en el motor V8 de Chrome que permite ejecutar JS en el servidor.
Características clave:
- Event-driven: Basado en eventos
- Non-blocking I/O: Operaciones asíncronas
- Single-threaded: Un hilo principal con event loop
- NPM: Gestor de paquetes más grande del mundo
Ejemplo:
javascript
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hola desde Node.js');
});
server.listen(3000, () => {
console.log('Servidor en puerto 3000');
});
Ventajas:
- JavaScript en frontend y backend
- Gran ecosistema (npm)
- Excelente para I/O intensivo
- Real-time applications (WebSockets)
22. Explica el patrón MVC en backend
Respuesta:
MVC (Model-View-Controller) separa la aplicación en tres componentes:
Model: Lógica de datos y base de datos
javascript
// models/User.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
nombre: String,
email: String
});
module.exports = mongoose.model('User', userSchema);
View: Presentación (en APIs, sería la respuesta JSON)
javascript
// En APIs REST, la "vista" es el JSON response
res.json({ success: true, data: usuarios });
Controller: Lógica de negocio
javascript
// controllers/userController.js
const User = require('../models/User');
exports.getUsers = async (req, res) => {
try {
const usuarios = await User.find();
res.json(usuarios);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Router: Conecta rutas con controllers
javascript
// routes/users.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/users', userController.getUsers);
module.exports = router;
23. ¿Qué es Express.js y por qué se usa?
Respuesta:
Express es un framework minimalista de Node.js para construir aplicaciones web y APIs.
Características:
- Routing robusto
- Middleware system
- Template engines
- Manejo de requests/responses simplificado
javascript
const express = require('express');
const app = express();
// Middleware
app.use(express.json());
// Rutas
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
app.post('/api/users', (req, res) => {
const nuevoUser = req.body;
res.status(201).json(nuevoUser);
});
// Error handling
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
app.listen(3000);
Ventajas:
- Ligero y flexible
- Gran ecosistema de middleware
- Curva de aprendizaje suave
- Estándar de facto en Node.js
24. ¿Qué son los middlewares en Express?
Respuesta:
Los middlewares son funciones que tienen acceso al objeto request (req), response (res), y la siguiente función middleware (next) en el ciclo de request/response.
javascript
// Middleware de logging
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // Pasa al siguiente middleware
};
// Middleware de autenticación
const auth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No autorizado' });
}
next();
};
// Uso
app.use(logger); // Aplica a todas las rutas
app.get('/api/protected', auth, (req, res) => {
res.json({ data: 'Datos protegidos' });
});
Tipos:
- Application-level:
app.use() - Router-level:
router.use() - Error-handling: Con 4 parámetros
(err, req, res, next) - Built-in:
express.json(),express.static() - Third-party:
cors,morgan, etc.
25. Explica REST API y sus principios
Respuesta:
REST (Representational State Transfer) es un estilo arquitectónico para diseñar APIs web.
Principios:
- Stateless: Cada request contiene toda la info necesaria
- Client-Server: Separación de responsabilidades
- Cacheable: Responses deben indicar si son cacheables
- Uniform Interface: Interfaz consistente
Métodos HTTP:
javascript
// GET - Obtener recursos
app.get('/api/posts', getPosts);
app.get('/api/posts/:id', getPostById);
// POST - Crear recurso
app.post('/api/posts', createPost);
// PUT - Actualizar completamente
app.put('/api/posts/:id', updatePost);
// PATCH - Actualizar parcialmente
app.patch('/api/posts/:id', partialUpdate);
// DELETE - Eliminar
app.delete('/api/posts/:id', deletePost);
```
**Códigos de respuesta comunes:**
- 200: OK
- 201: Created
- 400: Bad Request
- 401: Unauthorized
- 404: Not Found
- 500: Internal Server Error
### 26. ¿Qué es JWT y cómo funciona la autenticación?
**Respuesta:**
JWT (JSON Web Token) es un estándar para transmitir información de forma segura entre partes como un objeto JSON.
**Estructura:**
```
header.payload.signature
Implementación:
javascript
const jwt = require('jsonwebtoken');
// Generar token
const generarToken = (usuario) => {
return jwt.sign(
{ id: usuario.id, email: usuario.email },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
};
// Login
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const usuario = await User.findOne({ email });
if (!usuario) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
const passwordValido = await bcrypt.compare(password, usuario.password);
if (!passwordValido) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
const token = generarToken(usuario);
res.json({ token });
});
// Middleware de verificación
const verificarToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token no proporcionado' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.usuario = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Token inválido' });
}
};
27. ¿Cómo manejas errores en Node.js/Express?
Respuesta:
1. Try-catch en async/await:
javascript
app.get('/api/users/:id', async (req, res) => {
try {
const usuario = await User.findById(req.params.id);
if (!usuario) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}
res.json(usuario);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
2. Middleware de manejo de errores global:
javascript
// Middleware de error (siempre al final)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: {
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
3. Errores personalizados:
javascript
class ErrorPersonalizado extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
// Uso
if (!usuario) {
throw new ErrorPersonalizado('Usuario no encontrado', 404);
}
4. Async error wrapper:
javascript
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/api/users', asyncHandler(async (req, res) => {
const usuarios = await User.find();
res.json(usuarios);
}));
```
### 28. ¿Qué es CORS y cómo lo manejas?
**Respuesta:**
CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad que permite o restringe recursos solicitados desde otro dominio.
**Problema:**
```
Frontend en http://localhost:3000
Backend en http://localhost:5000
→ CORS error
Solución con el paquete cors:
javascript
const cors = require('cors');
// Permitir todos los orígenes (solo desarrollo)
app.use(cors());
// Configuración específica (producción)
app.use(cors({
origin: 'https://miapp.com',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Múltiples orígenes
const whitelist = ['https://miapp.com', 'https://admin.miapp.com'];
app.use(cors({
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error('No permitido por CORS'));
}
}
}));
29. Explica la diferencia entre SQL y NoSQL
Respuesta:
SQL (Relacional):
- Estructura: Tablas con filas y columnas
- Schema: Fijo y predefinido
- Relaciones: Foreign keys, JOINs
- Escalabilidad: Vertical (más potencia al servidor)
- Ejemplos: MySQL, PostgreSQL, SQL Server
sql
-- Ejemplo SQL SELECT u.nombre, p.titulo FROM usuarios u JOIN posts p ON u.id = p.user_id WHERE u.activo = true;
NoSQL (No relacional):
- Estructura: Documentos, key-value, grafos, columnas
- Schema: Flexible o dinámico
- Relaciones: Embebidas o referencias
- Escalabilidad: Horizontal (más servidores)
- Ejemplos: MongoDB, Redis, Cassandra
javascript
// Ejemplo MongoDB
db.usuarios.find({ activo: true });
Cuándo usar cada uno:
SQL:
- Datos estructurados con relaciones complejas
- Transacciones ACID críticas
- Reporting complejo
- Esquema estable
NoSQL:
- Datos no estructurados o semi-estructurados
- Escalabilidad masiva
- Desarrollo ágil (schema flexible)
- Caché, sesiones, real-time
30. ¿Cómo optimizas queries en bases de datos?
Respuesta:
1. Índices:
javascript
// MongoDB
userSchema.index({ email: 1 }); // Índice ascendente
userSchema.index({ nombre: 1, apellido: 1 }); // Compuesto
2. Selección de campos:
javascript
// ❌ Malo: Trae todos los campos
User.find();
// ✓ Bueno: Solo los campos necesarios
User.find().select('nombre email');
3. Paginación:
javascript
const page = req.query.page || 1; const limit = 10; const skip = (page - 1) * limit; const usuarios = await User.find() .limit(limit) .skip(skip);
4. Populate eficiente:
javascript
// ❌ Malo: Trae todos los campos de posts
User.findById(id).populate('posts');
// ✓ Bueno: Solo campos necesarios
User.findById(id).populate('posts', 'titulo fecha');
5. Aggregation pipeline:
javascript
User.aggregate([
{ $match: { activo: true } },
{ $group: { _id: '$ciudad', total: { $sum: 1 } } },
{ $sort: { total: -1 } }
]);
6. Caché:
javascript
const redis = require('redis');
const client = redis.createClient();
// Guardar en caché
app.get('/api/users', async (req, res) => {
const cached = await client.get('users');
if (cached) {
return res.json(JSON.parse(cached));
}
const usuarios = await User.find();
await client.setEx('users', 3600, JSON.stringify(usuarios));
res.json(usuarios);
});
Bases de Datos
31. Explica las relaciones en bases de datos
Respuesta:
One-to-One (1:1): Un usuario tiene un perfil
javascript
// SQL
CREATE TABLE usuarios (id, nombre);
CREATE TABLE perfiles (id, usuario_id UNIQUE, bio);
// MongoDB (embebido)
{
nombre: 'Ana',
perfil: { bio: '...', avatar: '...' }
}
One-to-Many (1:N): Un usuario tiene muchos posts
javascript
// SQL
CREATE TABLE usuarios (id, nombre);
CREATE TABLE posts (id, user_id, titulo);
// MongoDB (referencia)
{
_id: 'user1',
nombre: 'Ana'
}
{
_id: 'post1',
user_id: 'user1',
titulo: 'Mi post'
}
Many-to-Many (N:M): Estudiantes y cursos
javascript
// SQL (tabla intermedia)
CREATE TABLE estudiantes (id, nombre);
CREATE TABLE cursos (id, nombre);
CREATE TABLE inscripciones (estudiante_id, curso_id);
// MongoDB
{
_id: 'student1',
nombre: 'Juan',
cursos: ['curso1', 'curso2'] // Array de referencias
}
```
### 32. ¿Qué es normalización de bases de datos?
**Respuesta:**
Normalización es organizar datos para reducir redundancia y mejorar integridad.
**Formas normales:**
**1NF (Primera Forma Normal):**
- Valores atómicos (no arrays en celdas)
- Cada columna tiene un tipo de dato único
```
❌ usuarios: id, nombre, telefonos="123,456,789"
✓ usuarios: id, nombre
✓ telefonos: id, usuario_id, numero
2NF (Segunda Forma Normal):
- Cumple 1NF
- No hay dependencias parciales de la clave
3NF (Tercera Forma Normal):
- Cumple 2NF
- No hay dependencias transitivas
Ejemplo:
sql
-- ❌ Desnormalizado (redundancia) CREATE TABLE pedidos ( id INT, producto VARCHAR(100), categoria VARCHAR(50), precio DECIMAL, cliente VARCHAR(100), email_cliente VARCHAR(100) ); -- ✓ Normalizado CREATE TABLE clientes (id, nombre, email); CREATE TABLE productos (id, nombre, categoria, precio); CREATE TABLE pedidos (id, cliente_id, producto_id, fecha);
Cuándo desnormalizar:
- Reads muy frecuentes vs writes raros
- Performance crítica
- Data warehouse / reporting
33. ¿Qué son las transacciones ACID?
Respuesta:
ACID garantiza confiabilidad en transacciones de bases de datos:
Atomicity (Atomicidad): Todo o nada - si falla una parte, se revierte todo
javascript
// MongoDB
const session = await mongoose.startSession();
session.startTransaction();
try {
await User.updateOne({ _id: userId }, { $inc: { balance: -100 } }, { session });
await User.updateOne({ _id: receiverId }, { $inc: { balance: 100 } }, { session });
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Consistency (Consistencia): Los datos deben cumplir reglas de validación
Isolation (Aislamiento): Transacciones concurrentes no interfieren entre sí
Durability (Durabilidad): Datos confirmados persisten aunque falle el sistema
34. Explica índices en bases de datos
Respuesta:
Los índices son estructuras de datos que mejoran la velocidad de búsqueda.
Tipos:
Single field:
javascript
db.users.createIndex({ email: 1 });
Compound (múltiples campos):
javascript
db.products.createIndex({ category: 1, price: -1 });
Unique:
javascript
db.users.createIndex({ email: 1 }, { unique: true });
Text (búsqueda de texto):
javascript
db.posts.createIndex({ titulo: 'text', contenido: 'text' });
Ventajas:
- Queries más rápidos (especialmente en tablas grandes)
- Enforce uniqueness
- Sorting eficiente
Desventajas:
- Consume espacio en disco
- Ralentiza INSERT, UPDATE, DELETE
- Demasiados índices pueden ser contraproducentes
Best practices:
- Indexa campos usados frecuentemente en WHERE, JOIN, ORDER BY
- Usa EXPLAIN para analizar queries
- Evita indexar campos con baja cardinalidad (ej: género)
35. ¿Qué es un ORM y por qué usarlo?
Respuesta:
ORM (Object-Relational Mapping) mapea objetos de código a tablas de base de datos.
Ejemplos populares:
- Sequelize (SQL en Node.js)
- Mongoose (MongoDB en Node.js)
- TypeORM (TypeScript)
- Prisma (moderno, type-safe)
Mongoose ejemplo:
javascript
const userSchema = new mongoose.Schema({
nombre: { type: String, required: true },
email: { type: String, unique: true },
edad: { type: Number, min: 18 }
});
const User = mongoose.model('User', userSchema);
// Crear
const usuario = await User.create({ nombre: 'Ana', email: 'ana@email.com' });
// Leer
const usuarios = await User.find({ edad: { $gte: 18 } });
// Actualizar
await User.updateOne({ _id: id }, { nombre: 'Ana García' });
// Eliminar
await User.deleteOne({ _id: id });
Ventajas:
- Abstracción de la BD (cambias fácilmente entre SQL/NoSQL)
- Validaciones built-in
- Menos SQL manual
- Type safety (con TypeScript)
- Migrations automáticas
Desventajas:
- Curva de aprendizaje
- Queries complejas pueden ser difíciles
- Overhead de performance (en algunos casos)
Seguridad
36. ¿Cómo proteges contra ataques comunes?
Respuesta:
SQL Injection:
javascript
// ❌ Vulnerable
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✓ Usa prepared statements
const query = 'SELECT * FROM users WHERE email = ?';
db.query(query, [email]);
// ✓ Con ORM
User.findOne({ email });
XSS (Cross-Site Scripting):
javascript
// Sanitiza inputs
const sanitizeHtml = require('sanitize-html');
const clean = sanitizeHtml(userInput);
// Escapa output en templates
<p>{escape(userContent)}</p>
// Content Security Policy headers
app.use(helmet());
CSRF (Cross-Site Request Forgery):
javascript
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
Rate Limiting:
javascript
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100 // límite de requests
});
app.use('/api/', limiter);
Validación de inputs:
javascript
const { body, validationResult } = require('express-validator');
app.post('/register',
body('email').isEmail(),
body('password').isLength({ min: 8 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Procesar
}
);
37. ¿Cómo hasheas y validas contraseñas?
Respuesta:
NUNCA guardes contraseñas en texto plano. Usa bcrypt.
javascript
const bcrypt = require('bcrypt');
// Hashear contraseña (registro)
const hashPassword = async (password) => {
const saltRounds = 10;
return await bcrypt.hash(password, saltRounds);
};
// Registro de usuario
app.post('/register', async (req, res) => {
const { email, password } = req.body;
// Validar fuerza de contraseña
if (password.length < 8) {
return res.status(400).json({ error: 'Contraseña muy corta' });
}
const hashedPassword = await hashPassword(password);
const usuario = await User.create({
email,
password: hashedPassword
});
res.status(201).json({ message: 'Usuario creado' });
});
// Login - validar contraseña
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const usuario = await User.findOne({ email });
if (!usuario) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
const passwordValido = await bcrypt.compare(password, usuario.password);
if (!passwordValido) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
// Generar token
const token = jwt.sign({ id: usuario.id }, process.env.JWT_SECRET);
res.json({ token });
});
Best practices:
- Salt rounds: 10-12 (más alto = más seguro pero más lento)
- Nunca uses MD5 o SHA-1 para contraseñas
- Implementa rate limiting en login
- Mensajes de error genéricos ("Credenciales inválidas" en lugar de "Email no existe")
38. ¿Qué es HTTPS y por qué es importante?
Respuesta:
HTTPS (HTTP Secure) encripta la comunicación entre cliente y servidor usando SSL/TLS.
Por qué es crítico:
- Protege datos sensibles (contraseñas, tarjetas)
- Previene man-in-the-middle attacks
- Autenticación del servidor
- Requisito para PWAs, HTTP/2, algunas APIs
Implementación con Express:
javascript
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem')
};
https.createServer(options, app).listen(443);
En producción:
- Usa Let's Encrypt (certificados gratuitos)
- Configura redirect HTTP → HTTPS
- Usa HSTS headers
- Configuración SSL/TLS moderna
39. Explica OAuth 2.0
Respuesta:
OAuth 2.0 es un protocolo de autorización que permite a apps de terceros acceder a recursos sin compartir contraseñas.
Flujo básico (Authorization Code):
- Usuario hace clic en "Login con Google"
- Redirige a Google con client_id y redirect_uri
- Usuario autoriza permisos
- Google redirige de vuelta con un código
- Tu servidor intercambia código por access token
- Usas token para acceder a recursos del usuario
javascript
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
},
(accessToken, refreshToken, profile, done) => {
// Buscar o crear usuario
User.findOrCreate({ googleId: profile.id }, (err, user) => {
return done(err, user);
});
}));
// Rutas
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/dashboard');
}
);
Componentes:
- Resource Owner: Usuario
- Client: Tu aplicación
- Authorization Server: Google, Facebook, etc.
- Resource Server: API con los datos del usuario
40. ¿Qué son las variables de entorno?
Respuesta:
Variables de entorno almacenan configuración sensible fuera del código.
Uso con dotenv:
javascript
// .env (NUNCA commitear a git)
DATABASE_URL=mongodb://localhost:27017/miapp
JWT_SECRET=supersecretkey123
PORT=5000
NODE_ENV=development
// .gitignore
.env
// app.js
require('dotenv').config();
const dbUrl = process.env.DATABASE_URL;
const port = process.env.PORT || 3000;
mongoose.connect(process.env.DATABASE_URL);
app.listen(port, () => {
console.log(`Servidor en puerto ${port}`);
});
Qué guardar en .env:
- API keys
- Database credentials
- JWT secrets
- Third-party service tokens
- Configuración específica del entorno
Best practices:
- Nunca hardcodees secretos en el código
- Usa diferentes .env para dev/staging/prod
- Documenta variables requeridas en .env.example
- En producción, usa servicios como AWS Secrets Manager
Testing y Herramientas
41. ¿Qué tipos de testing conoces?
Respuesta:
Unit Testing: Prueba funciones/métodos individuales
javascript
// sum.test.js (Jest)
const sum = (a, b) => a + b;
test('suma 1 + 2 es 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('suma números negativos', () => {
expect(sum(-1, -2)).toBe(-3);
});
Integration Testing: Prueba cómo interactúan componentes
javascript
// api.test.js
const request = require('supertest');
const app = require('../app');
describe('POST /api/users', () => {
it('crea un usuario nuevo', async () => {
const res = await request(app)
.post('/api/users')
.send({ nombre: 'Ana', email: 'ana@test.com' });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty('id');
});
});
E2E (End-to-End): Prueba flujos completos desde la perspectiva del usuario
javascript
// Cypress
describe('Login flow', () => {
it('permite login con credenciales válidas', () => {
cy.visit('/login');
cy.get('input[name=email]').type('user@test.com');
cy.get('input[name=password]').type('password123');
cy.get('button[type=submit]').click();
cy.url().should('include', '/dashboard');
});
});
```
**Pirámide de testing:**
```
/\
/E2E\ Pocos, lentos, costosos
/------\
/Integra\ Moderados
/----------\
/ Unit Tests \ Muchos, rápidos, baratos
42. Explica TDD (Test-Driven Development)
Respuesta:
TDD es escribir tests ANTES del código de producción.
Ciclo Red-Green-Refactor:
- Red: Escribe un test que falle
javascript
test('usuario puede registrarse', async () => {
const response = await register('ana@test.com', 'pass123');
expect(response.success).toBe(true);
});
// ❌ Falla porque register() no existe
- Green: Escribe el código mínimo para pasar el test
javascript
const register = async (email, password) => {
return { success: true };
};
// ✓ Pasa
- Refactor: Mejora el código sin romper tests
javascript
const register = async (email, password) => {
if (!email || !password) throw new Error('Faltan datos');
const user = await User.create({ email, password: hash(password) });
return { success: true, userId: user.id };
};
Beneficios:
- Código más testeable
- Menos bugs
- Documentación viva
- Confianza para refactorizar
43. ¿Qué es Git y comandos esenciales?
Respuesta:
Git es un sistema de control de versiones distribuido.
Comandos básicos:
bash
# Inicializar repo git init # Clonar proyecto git clone https://github.com/user/repo.git # Ver estado git status # Agregar cambios al staging git add . # Todos los archivos git add archivo.js # Archivo específico # Commit git commit -m "Mensaje descriptivo" # Ver historial git log git log --oneline # Branches git branch # Ver branches git branch feature-login # Crear branch git checkout feature-login # Cambiar a branch git checkout -b new-branch # Crear y cambiar # Merge git checkout main git merge feature-login # Push/Pull git push origin main git pull origin main # Deshacer cambios git checkout -- archivo.js # Descartar cambios git reset HEAD archivo.js # Quitar del staging git revert commit_hash # Revertir commit
Flujo Git típico:
bash
git checkout -b feature/nueva-funcionalidad # ... hacer cambios ... git add . git commit -m "feat: agregar nueva funcionalidad" git push origin feature/nueva-funcionalidad # ... crear Pull Request en GitHub ...
44. ¿Qué es CI/CD?
Respuesta:
CI (Continuous Integration): Integrar cambios de código frecuentemente con tests automáticos
CD (Continuous Deployment/Delivery): Desplegar automáticamente a producción
GitHub Actions ejemplo:
yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build
run: npm run build
Beneficios:
- Detección temprana de bugs
- Deployments más frecuentes y confiables
- Menos trabajo manual
- Feedback rápido a desarrolladores
45. ¿Qué es Docker?
Respuesta:
Docker empaqueta aplicaciones con todas sus dependencias en contenedores portables.
Dockerfile ejemplo:
dockerfile
# Imagen base FROM node:18-alpine # Directorio de trabajo WORKDIR /app # Copiar package.json COPY package*.json ./ # Instalar dependencias RUN npm install # Copiar código COPY . . # Exponer puerto EXPOSE 3000 # Comando de inicio CMD ["npm", "start"]
docker-compose.yml:
yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=mongodb://mongo:27017/miapp
depends_on:
- mongo
mongo:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
Comandos básicos:
bash
# Construir imagen docker build -t miapp . # Correr contenedor docker run -p 3000:3000 miapp # Ver contenedores docker ps # Con docker-compose docker-compose up docker-compose down
Ventajas:
- Mismo entorno en dev, staging, prod
- Fácil escalabilidad
- Aislamiento de dependencias
- Deploy consistente
Conceptos Avanzados
46. ¿Qué es el patrón Repository?
Respuesta:
Repository abstrae la lógica de acceso a datos, separándola de la lógica de negocio.
javascript
// repositories/userRepository.js
class UserRepository {
async findAll() {
return await User.find();
}
async findById(id) {
return await User.findById(id);
}
async findByEmail(email) {
return await User.findOne({ email });
}
async create(userData) {
return await User.create(userData);
}
async update(id, userData) {
return await User.findByIdAndUpdate(id, userData, { new: true });
}
async delete(id) {
return await User.findByIdAndDelete(id);
}
}
module.exports = new UserRepository();
// services/userService.js
const userRepository = require('../repositories/userRepository');
class UserService {
async registerUser(email, password) {
// Validaciones de negocio
const existingUser = await userRepository.findByEmail(email);
if (existingUser) {
throw new Error('Email ya registrado');
}
const hashedPassword = await bcrypt.hash(password, 10);
return await userRepository.create({ email, password: hashedPassword });
}
}
// controllers/userController.js
exports.register = async (req, res) => {
try {
const usuario = await userService.registerUser(req.body.email, req.body.password);
res.status(201).json(usuario);
} catch (error) {
res.status(400).json({ error: error.message });
}
};
Benefijas:
- Fácil de testear (puedes mockear el repository)
- Cambiar la BD sin tocar lógica de negocio
- Código más organizado y mantenible
47. ¿Qué es GraphQL?
Respuesta:
GraphQL es un lenguaje de consulta para APIs que permite al cliente pedir exactamente los datos que necesita.
Diferencias con REST:
REST:
javascript
GET /api/users/1
{
"id": 1,
"nombre": "Ana",
"email": "ana@email.com",
"telefono": "123456",
"direccion": {...},
"posts": [...] // Quizás no necesitas esto
}
GraphQL:
graphql
query {
user(id: 1) {
nombre
email
}
}
# Respuesta: solo lo que pediste
{
"data": {
"user": {
"nombre": "Ana",
"email": "ana@email.com"
}
}
}
Implementación básica:
javascript
const { ApolloServer, gql } = require('apollo-server');
// Schema
const typeDefs = gql`
type User {
id: ID!
nombre: String!
email: String!
posts: [Post]
}
type Post {
id: ID!
titulo: String!
autor: User!
}
type Query {
users: [User]
user(id: ID!): User
}
type Mutation {
createUser(nombre: String!, email: String!): User
}
`;
// Resolvers
const resolvers = {
Query: {
users: () => User.find(),
user: (_, { id }) => User.findById(id)
},
Mutation: {
createUser: (_, { nombre, email }) => User.create({ nombre, email })
},
User: {
posts: (user) => Post.find({ autorId: user.id })
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => console.log(`Server en ${url}`));
```
**Ventajas:**
- Un solo endpoint
- Cliente pide exactamente lo que necesita (no overfetching)
- Fuertemente tipado
- Introspection (documentación automática)
**Desventajas:**
- Curva de aprendizaje
- Caché más complejo que REST
- Puede ser overkill para APIs simples
### 48. ¿Qué es WebSocket y cuándo usarlo?
**Respuesta:**
WebSocket es un protocolo de comunicación bidireccional en tiempo real sobre una sola conexión TCP.
**HTTP vs WebSocket:**
```
HTTP: Cliente → Request → Servidor → Response
WebSocket: Cliente ↔ Conexión persistente ↔ Servidor
Implementación con Socket.io:
javascript
// Server
const io = require('socket.io')(server);
io.on('connection', (socket) => {
console.log('Usuario conectado:', socket.id);
// Escuchar eventos
socket.on('mensaje', (data) => {
console.log('Mensaje recibido:', data);
// Emitir a todos
io.emit('mensaje', data);
// Emitir a todos excepto el emisor
socket.broadcast.emit('mensaje', data);
});
socket.on('disconnect', () => {
console.log('Usuario desconectado');
});
});
// Client (React)
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
useEffect(() => {
socket.on('mensaje', (data) => {
setMensajes(prev => [...prev, data]);
});
return () => socket.disconnect();
}, []);
const enviarMensaje = (texto) => {
socket.emit('mensaje', { texto, autor: usuario });
};
```
**Casos de uso:**
- Chat en tiempo real
- Notificaciones push
- Juegos multijugador
- Dashboards con datos en vivo
- Colaboración en tiempo real (Google Docs)
**Cuándo NO usar:**
- Si HTTP polling es suficiente
- Datos que no cambian frecuentemente
- Necesitas caché agresivo
### 49. Explica microservicios vs monolito
**Respuesta:**
**Monolito:**
Una sola aplicación con toda la lógica
```
[Frontend + Backend + DB Logic] → DB
```
**Microservicios:**
Múltiples servicios independientes
```
Frontend → API Gateway →
├ Auth Service → Auth DB
├ User Service → User DB
├ Product Service → Product DB
└ Order Service → Order DB
Ventajas de microservicios:
- Escalabilidad independiente
- Tecnologías diferentes por servicio
- Deploys independientes
- Equipos autónomos
- Fallas aisladas
Desventajas:
- Complejidad operacional
- Debugging más difícil
- Latencia de red entre servicios
- Transacciones distribuidas complejas
Cuándo usar monolito:
- Startups/MVPs
- Equipos pequeños
- Dominio simple
- Comenzar rápido
Cuándo considerar microservicios:
- App madura que necesita escalar
- Equipos grandes
- Diferentes partes con requisitos distintos
- Necesitas deploys independientes
50. ¿Qué es Serverless?
Respuesta:
Serverless (Functions as a Service) ejecuta código sin gestionar servidores.
AWS Lambda ejemplo:
javascript
// handler.js
exports.handler = async (event) => {
const { nombre } = JSON.parse(event.body);
return {
statusCode: 200,
body: JSON.stringify({ mensaje: `Hola, ${nombre}` })
};
};
Características:
- Paga solo por ejecución (no por servidor en espera)
- Auto-escalado automático
- No gestión de infraestructura
- Stateless (sin estado entre invocaciones)
Casos de uso:
- APIs con tráfico variable
- Procesamiento de eventos (uploads, webhooks)
- Scheduled tasks (cron jobs)
- Backend para apps móviles
Limitaciones:
- Cold starts (primera invocación lenta)
- Tiempo de ejecución limitado
- Vendor lock-in
- Debugging complejo
Plataformas:
- AWS Lambda
- Google Cloud Functions
- Azure Functions
- Vercel Functions
- Netlify Functions
Conclusión
Estas 50 preguntas cubren los conceptos fundamentales que todo desarrollador Full Stack debe dominar. La clave para entrevistas exitosas no es memorizar respuestas, sino entender los conceptos profundamente y poder explicarlos con claridad.
Tips finales para entrevistas:
- Practica en voz alta - Explica conceptos a un amigo o frente al espejo
- Construye proyectos - La teoría sin práctica no sirve
- Haz preguntas - Las entrevistas son bidireccionales
- Sé honesto - Si no sabes algo, dilo y muestra disposición a aprender
- Usa el método STAR - Situación, Tarea, Acción, Resultado para preguntas de comportamiento
Recursos para seguir preparándote:
- LeetCode / HackerRank para algoritmos
- Sistema de diseño (System Design Primer)
- Documentación oficial de tecnologías
- Proyectos personales en GitHub
Próximos pasos:
- Revisa las preguntas que no dominaste
- Implementa ejemplos de código tú mismo
- Practica mock interviews con amigos o plataformas