{
  "meta": {
    "programa": "Polyglot Programming Labs",
    "total_preguntas": 90,
    "partes": 12
  },
  "partes": [
    {
      "idx": 0,
      "titulo": "Pensamiento computacional y el método políglota",
      "preguntas": [
        {
          "q": "Al portar código de Python a Rust, una variable que se reasignaba libremente ahora no compila. ¿De qué clase de diferencia se trata y cuál es el arreglo idiomático?",
          "opciones": [
            "Sintáctica: basta con cambiar el nombre de la variable",
            "Semántica: Rust es inmutable por defecto; declarar la variable con `let mut`",
            "Paradigmática: hay que reescribir el programa en estilo funcional",
            "Es un bug del compilador de Rust"
          ],
          "correcta": 1,
          "exp": "La mutabilidad por defecto es una diferencia semántica: cambia el comportamiento del enlace nombre→valor. Rust exige `let mut` para permitir la reasignación."
        },
        {
          "q": "¿Cuál de estas afirmaciones describe mejor la tesis del programa?",
          "opciones": [
            "Todos los lenguajes son iguales y solo cambia la sintaxis",
            "Hay que aprender los 10 lenguajes por separado antes de compararlos",
            "El concepto es transferible; se aprende una vez y se reconoce, compara y aplica en cada lenguaje",
            "SQL no cuenta como lenguaje de programación"
          ],
          "correcta": 2,
          "exp": "El programa enseña el conocimiento transferible: el concepto permanece, la forma cambia. No afirma que los lenguajes sean iguales; explica qué cambia y por qué."
        },
        {
          "q": "El verificador de equivalencia omite un lenguaje. ¿Qué significa?",
          "opciones": [
            "La implementación de ese lenguaje está mal",
            "El toolchain de ese lenguaje no está instalado; se informa y se continúa",
            "El caso de prueba es inválido",
            "El programa no soporta ese lenguaje"
          ],
          "correcta": 1,
          "exp": "El verificador degrada silenciosamente: si falta el toolchain, marca el lenguaje como omitido e informa, sin fallar. Solo falla si una implementación presente produce una salida distinta."
        },
        {
          "q": "Un algoritmo O(n^2) escrito en C se vuelve más lento que uno O(n log n) escrito en Python cuando n crece lo suficiente. ¿Qué idea del pensamiento computacional ilustra este hecho?",
          "opciones": [
            "Que Python es en realidad más rápido que C",
            "Que la complejidad solo importa en lenguajes interpretados",
            "Que el orden de crecimiento domina sobre el factor constante del lenguaje: elegir el algoritmo pesa más que elegir el lenguaje",
            "Que medir el tiempo de ejecución no sirve para nada"
          ],
          "correcta": 2,
          "exp": "El lenguaje aporta un factor constante; la complejidad describe cómo escala el coste con el tamaño de la entrada. A partir de cierto n el término dominante gana siempre, así que la decisión algorítmica es la palanca principal."
        },
        {
          "q": "El programa insiste en escribir primero pseudocódigo neutral, sin comprometerse con ningún lenguaje. ¿Cuál es la razón metodológica principal?",
          "opciones": [
            "Separar la decisión algorítmica de la decisión de forma, para que la solución pueda expresarse después en cualquier lenguaje y se vea qué cambia al traducirla",
            "Porque el pseudocódigo se ejecuta más rápido que el código real",
            "Porque los lenguajes reales no permiten expresar algoritmos complejos",
            "Porque así se evita tener que probar el programa"
          ],
          "correcta": 0,
          "exp": "El pseudocódigo aísla el QUÉ del CÓMO idiomático. Al no arrastrar los reflejos de un lenguaje concreto, la traducción posterior revela con nitidez qué diferencias son de forma y cuáles son de semántica."
        },
        {
          "q": "Trazas a mano un algoritmo con una tabla de variables por iteración y descubres un error antes de ejecutarlo. ¿Por qué el trazado manual sigue siendo valioso teniendo depurador?",
          "opciones": [
            "Porque el depurador solo funciona en lenguajes compilados",
            "Porque el trazado manual es siempre más rápido que ejecutar el programa",
            "Porque evita tener que escribir pruebas",
            "Porque obliga a construir un modelo mental del estado que el depurador solo te muestra: entiendes por qué falla, no solo dónde"
          ],
          "correcta": 3,
          "exp": "El depurador informa del estado observado; el trazado obliga a predecirlo. La discrepancia entre lo que esperabas y lo que ocurre es la que localiza el error de razonamiento, y ese modelo mental es el que se transfiere a cualquier lenguaje."
        },
        {
          "q": "Traduces un bucle índice-por-índice de C a Python conservando `for i in range(len(xs)): ...`. Funciona, pero se considera poco idiomático. ¿Qué se pierde con la traducción literal?",
          "opciones": [
            "Nada: si el resultado es correcto, el estilo es irrelevante",
            "Se pierde legibilidad y seguridad: el idioma del lenguaje (iterar el elemento directamente) evita errores de índice y comunica la intención a quien lee",
            "Se pierde la posibilidad de usar variables",
            "Se pierde la corrección: el programa dará resultados distintos"
          ],
          "correcta": 1,
          "exp": "La traducción literal produce código correcto pero ajeno: arrastra el andamiaje de un lenguaje a otro que ya lo resuelve. Lo idiomático reduce superficie de error (fuera de rango, off-by-one) y hace explícita la intención."
        },
        {
          "q": "Antes de implementar, el método pide enumerar casos límite (entrada vacía, un solo elemento, valores negativos, desbordamiento). ¿Por qué ese paso pertenece al análisis y no a la fase de pruebas?",
          "opciones": [
            "Porque las pruebas se escriben siempre después de entregar",
            "Porque los casos límite solo afectan a lenguajes con tipado estático",
            "Porque los casos límite forman parte de la especificación: definen qué debe hacer el programa y a menudo cambian el diseño del algoritmo, no solo su verificación",
            "Porque cada lenguaje maneja los casos límite automáticamente"
          ],
          "correcta": 2,
          "exp": "Decidir qué ocurre con la entrada vacía o fuera de rango es definir el contrato del programa. Descubrirlo tarde obliga a rediseñar; hacerlo antes convierte las restricciones en parte del algoritmo y luego las pruebas solo confirman lo ya especificado."
        }
      ]
    },
    {
      "idx": 1,
      "titulo": "Atlas y genealogía de los lenguajes",
      "preguntas": [
        {
          "q": "Se dice que C# y Java son \"primos\" y que ambos descienden de C en la sintaxis. ¿Qué captura mejor el valor de pensar en familias de lenguajes al aprender uno nuevo?",
          "opciones": [
            "Que dos lenguajes de la misma familia comparten siempre la misma biblioteca estándar",
            "Que conocer los rasgos de la familia (gestión de memoria, tipado, modelo de ejecución) permite anticipar decisiones de diseño del nuevo lenguaje",
            "Que la sintaxis parecida garantiza que el código de uno compile en el otro",
            "Que los lenguajes de una familia tienen exactamente el mismo rendimiento"
          ],
          "correcta": 1,
          "exp": "La genealogía es útil porque predice decisiones de diseño (memoria administrada por GC, tipado estático nominal, VM), no porque garantice compatibilidad de código o bibliotecas. La sintaxis compartida es solo la capa más visible."
        },
        {
          "q": "Go, Erlang/Elixir y Java resuelven la concurrencia de maneras distintas. ¿Qué idea genealógica explica por qué Go se asocia al CSP y Erlang al modelo de actores?",
          "opciones": [
            "Ambos copiaron el modelo de hilos con memoria compartida de C",
            "Go hereda la comunicación por canales (CSP de Hoare) y Erlang el paso de mensajes entre procesos aislados (actores)",
            "Los dos usan exactamente el mismo modelo, solo cambia el nombre",
            "La concurrencia no depende del linaje del lenguaje sino solo del hardware"
          ],
          "correcta": 1,
          "exp": "Go desciende de la tradición CSP: goroutines que se comunican por canales. Erlang encarna el modelo de actores: procesos aislados sin memoria compartida que intercambian mensajes. Son linajes conceptuales distintos, no el modelo de hilos compartidos de C."
        },
        {
          "q": "Rust incorpora enums con datos, pattern matching y Option/Result. ¿De qué familia toma prestadas esas ideas y qué revela sobre la evolución de los lenguajes?",
          "opciones": [
            "De la familia Lisp; demuestra que Rust es un dialecto de Scheme",
            "De la familia funcional tipada (ML/Haskell); los lenguajes modernos combinan linajes en vez de pertenecer a uno solo",
            "De COBOL; prueba que las ideas viejas no sirven",
            "De SQL; los tipos algebraicos vienen del álgebra relacional"
          ],
          "correcta": 1,
          "exp": "Los tipos suma con datos, el pattern matching y Option/Result provienen de la familia ML (Haskell, OCaml). Rust es un caso claro de lenguaje moderno que fusiona el linaje de sistemas (C/C++) con el funcional tipado."
        },
        {
          "q": "SQL aparece en el núcleo junto a lenguajes imperativos. ¿Por qué se le considera un lenguaje de programación pese a no tener bucles ni variables mutables al estilo de Python?",
          "opciones": [
            "Porque en el fondo se compila a C",
            "Porque es declarativo: se describe QUÉ resultado se quiere y el motor decide CÓMO obtenerlo, lo cual es un paradigma legítimo",
            "Porque solo sirve para almacenar datos, no para programar",
            "Porque es idéntico a una hoja de cálculo"
          ],
          "correcta": 1,
          "exp": "SQL pertenece a la familia declarativa/lógica: el programador especifica el resultado deseado y el planificador de consultas elige la estrategia de ejecución. Es un paradigma de programación distinto al imperativo, no una carencia."
        },
        {
          "q": "JavaScript se define por el estándar ECMAScript y C++ por una norma ISO, mientras que Python se define en la práctica por CPython más los PEP. ¿Qué consecuencia práctica tiene esta diferencia?",
          "opciones": [
            "Que los lenguajes con estándar no cambian nunca",
            "Que un lenguaje sin estándar no puede usarse en producción",
            "Que la norma ISO garantiza que todos los compiladores generen el mismo binario",
            "Que con un estándar formal pueden coexistir varias implementaciones compatibles, mientras que con una implementación de referencia esta define de facto el comportamiento, incluidos sus detalles internos"
          ],
          "correcta": 3,
          "exp": "El estándar describe el comportamiento exigible y habilita implementaciones alternativas (V8, SpiderMonkey; GCC, Clang, MSVC). Con implementación de referencia, detalles no especificados de CPython terminan siendo el contrato observado, lo que complica alternativas como PyPy."
        },
        {
          "q": "C, C++ y Objective-C comparten la familia de las llaves y una base común, pero la compatibilidad entre ellos no es simétrica. ¿Qué afirmación describe mejor la relación?",
          "opciones": [
            "C++ y Objective-C extienden C tomando cada uno un modelo de objetos distinto (clases con plantillas y RAII frente a mensajes al estilo Smalltalk), por lo que casi todo C válido compila, pero el código de las extensiones no se cruza",
            "Los tres son el mismo lenguaje con tres nombres comerciales",
            "Objective-C es un subconjunto estricto de C++",
            "C es una versión moderna simplificada de C++"
          ],
          "correcta": 0,
          "exp": "Ambos parten de C y añaden objetos, pero por linajes distintos: C++ desde Simula (clases, plantillas, destructores deterministas) y Objective-C desde Smalltalk (envío dinámico de mensajes). La base C es común; las capas OO son incompatibles entre sí."
        },
        {
          "q": "Python, Ruby, Perl y PHP se agrupan como scripting dinámico. ¿Qué rasgos compartidos justifican esa agrupación más allá del parecido superficial?",
          "opciones": [
            "Que los cuatro se compilan a código máquina nativo",
            "Que los cuatro exigen declarar el tipo de cada variable",
            "Tipado dinámico, gestión automática de memoria, ejecución sin paso de compilación explícito y una fuerte orientación a productividad y manipulación de texto",
            "Que los cuatro fueron creados por la misma empresa"
          ],
          "correcta": 2,
          "exp": "La familia se define por decisiones de diseño convergentes: dinamismo, memoria administrada, ciclo editar-ejecutar inmediato y raíces en automatización y procesamiento de texto. Reconocerlas permite anticipar cómo se comportará cualquier miembro nuevo de la familia."
        },
        {
          "q": "TypeScript nace sobre el runtime de JavaScript y Kotlin sobre la JVM, en lugar de crear su propia plataforma. ¿Qué explica esta estrategia recurrente?",
          "opciones": [
            "Que crear un runtime nuevo es técnicamente imposible",
            "Que heredar un runtime consolidado da acceso inmediato a su ecosistema de bibliotecas, herramientas y plataformas de despliegue, a cambio de aceptar sus límites semánticos",
            "Que ambos lenguajes son idénticos a su anfitrión salvo por la sintaxis",
            "Que los runtimes existentes son más rápidos que cualquier alternativa posible"
          ],
          "correcta": 1,
          "exp": "El coste real de un lenguaje nuevo no es el compilador sino el ecosistema. Apoyarse en un runtime existente lo resuelve de golpe, pero obliga a convivir con su modelo de objetos, su recolector y sus rarezas (por eso TS no puede eliminar la coerción de JS en ejecución)."
        }
      ]
    },
    {
      "idx": 2,
      "titulo": "Herramientas, toolchains y anatomía de comandos",
      "preguntas": [
        {
          "q": "En un lenguaje interpretado corregiste un error de tipos escribiendo mal el nombre de una variable que solo se usa en una rama poco frecuente. ¿Por qué ese mismo error habría aparecido antes en un lenguaje compilado con tipado estático?",
          "opciones": [
            "Porque los lenguajes compilados no permiten variables",
            "Porque la compilación analiza todo el código antes de ejecutar, mientras que el intérprete solo evalúa las líneas que realmente corre",
            "Porque los intérpretes no tienen errores de tipos nunca",
            "Porque compilar borra las ramas poco frecuentes"
          ],
          "correcta": 1,
          "exp": "El compilador verifica todo el programa de forma estática antes de ejecutar, así que detecta errores en ramas no ejecutadas. Un intérprete descubre el error solo cuando el flujo llega a esa línea en tiempo de ejecución."
        },
        {
          "q": "TypeScript se \"transpila\" a JavaScript, Java se compila a bytecode y C se compila a código máquina. ¿Qué distingue conceptualmente a la transpilación de la compilación tradicional?",
          "opciones": [
            "La transpilación produce código de otro lenguaje de alto nivel (o de nivel similar), no un ejecutable ni código máquina",
            "La transpilación siempre es más lenta en tiempo de ejecución",
            "La transpilación elimina la necesidad de un runtime",
            "No hay ninguna diferencia; son sinónimos exactos"
          ],
          "correcta": 0,
          "exp": "Transpilar es traducir de un lenguaje fuente a otro de nivel comparable (TS→JS). Compilar tradicionalmente baja el nivel de abstracción hacia bytecode o código máquina. El destino de la traducción es lo que los distingue."
        },
        {
          "q": "Herramientas como pyenv, nvm, rustup y SDKMAN existen en casi todos los ecosistemas. ¿Qué problema transferible resuelven todas ellas?",
          "opciones": [
            "Formatear el código automáticamente al guardar",
            "Instalar y alternar entre múltiples versiones del lenguaje/toolchain por proyecto sin conflictos globales",
            "Ejecutar las pruebas unitarias en paralelo",
            "Publicar el paquete en el repositorio oficial"
          ],
          "correcta": 1,
          "exp": "Son gestores de versiones del propio toolchain: permiten que distintos proyectos usen distintas versiones del lenguaje sin romper la instalación global. El concepto es idéntico entre ecosistemas aunque cambie el comando."
        },
        {
          "q": "Al leer `cargo build --release --target wasm32-unknown-unknown`, ¿qué parte es el subcomando y qué parte es una bandera (flag)?",
          "opciones": [
            "`cargo` es el subcomando y `build` es una bandera",
            "`build` es el subcomando y `--release` es una bandera; `wasm32-unknown-unknown` es el argumento de `--target`",
            "Todo después de `cargo` son argumentos posicionales sin estructura",
            "`--release` y `--target` son subcomandos anidados"
          ],
          "correcta": 1,
          "exp": "La anatomía es: programa (`cargo`), subcomando (`build`), banderas (`--release`, `--target`) y el valor que acompaña a `--target`. Reconocer este esquema permite leer comandos de cualquier toolchain."
        },
        {
          "q": "Casi todo lenguaje dinámico ofrece un REPL, y algunos compilados no. ¿Qué aporta realmente un REPL al aprendizaje y la depuración?",
          "opciones": [
            "Sustituye por completo a las pruebas automatizadas",
            "Compila el programa a un binario optimizado",
            "Permite un ciclo de retroalimentación inmediato: evaluar una expresión y ver su valor y su tipo sin escribir un programa completo ni pasar por el ciclo de compilación",
            "Garantiza que el código escrito allí funcionará igual en producción"
          ],
          "correcta": 2,
          "exp": "El valor del REPL es acortar el bucle hipótesis-experimento-resultado. Es una herramienta de exploración, no de verificación: lo que se aprende allí debe consolidarse en código y pruebas, porque el estado acumulado de la sesión puede engañar."
        },
        {
          "q": "Un proyecto usa Black en Python, Prettier en JS y gofmt en Go, y además ejecuta un linter en CI. ¿Qué distingue a un formateador de un linter?",
          "opciones": [
            "El formateador reescribe la presentación del código (espaciado, saltos, comillas) sin alterar su significado; el linter analiza el código para señalar posibles errores, riesgos o malas prácticas",
            "El formateador detecta bugs y el linter solo alinea el código",
            "Son sinónimos: cambian de nombre según el lenguaje",
            "El linter compila el código y el formateador lo ejecuta"
          ],
          "correcta": 0,
          "exp": "El formateador normaliza la forma y elimina las discusiones de estilo del code review; el linter razona sobre el contenido (variables sin usar, comparaciones sospechosas, patrones peligrosos). Se complementan y ambos existen en prácticamente todo ecosistema."
        },
        {
          "q": "Instalas un paquete y el gestor descarga otros veinte que no pediste. ¿Qué concepto explica esto y qué riesgo introduce?",
          "opciones": [
            "Es un fallo del gestor: solo debería instalar lo pedido",
            "Son copias de seguridad del paquete principal",
            "Son traducciones del paquete a otros lenguajes",
            "Son dependencias transitivas: las dependencias de tus dependencias; amplían la superficie de confianza y de vulnerabilidades, y pueden entrar en conflicto de versiones entre sí"
          ],
          "correcta": 3,
          "exp": "El grafo de dependencias es recursivo. Cada nodo transitivo es código de terceros que ejecutas y debes auditar, y dos ramas pueden exigir versiones incompatibles del mismo paquete: por eso existen los resolutores de versiones y los lockfiles."
        },
        {
          "q": "Tras instalar una herramienta, la terminal responde \"command not found\" aunque el binario existe en el disco. ¿Cuál es la causa más probable y por qué es un problema transferible?",
          "opciones": [
            "El binario está corrupto y hay que recompilarlo desde el código fuente",
            "El directorio del binario no está en la variable de entorno PATH, que es la lista de rutas donde el shell busca ejecutables en cualquier sistema operativo",
            "El lenguaje no está soportado por ese sistema operativo",
            "Falta ejecutar el programa como administrador"
          ],
          "correcta": 1,
          "exp": "El shell no busca en todo el disco: recorre las rutas de PATH en orden. El síntoma y la solución son idénticos en Windows y en Unix aunque cambie el separador y la forma de editarlo; también explica por qué a veces se ejecuta una versión antigua que aparece antes en la lista."
        }
      ]
    },
    {
      "idx": 3,
      "titulo": "Valores, tipos y variables",
      "preguntas": [
        {
          "q": "En JavaScript `\"5\" + 3` da `\"53\"` pero `\"5\" - 3` da `2`, mientras que en Python `\"5\" + 3` lanza un error. ¿Qué propiedad del sistema de tipos explica la diferencia?",
          "opciones": [
            "Python es compilado y JavaScript interpretado",
            "JavaScript tiene tipado débil con coerción implícita; Python tiene tipado fuerte que rechaza mezclar tipos incompatibles",
            "JavaScript no tiene tipos de datos",
            "Python convierte todo a cadena antes de operar"
          ],
          "correcta": 1,
          "exp": "El eje fuerte/débil describe cuánta coerción implícita permite el lenguaje. JS coacciona agresivamente según el operador; Python es fuertemente tipado y exige conversión explícita. Es independiente de estático/dinámico."
        },
        {
          "q": "Sumar 0.1 + 0.2 da 0.30000000000000004 en Python, JavaScript, Java y C. ¿Por qué el mismo \"error\" aparece en lenguajes tan distintos?",
          "opciones": [
            "Porque todos tienen el mismo bug heredado de C",
            "Porque todos usan el estándar IEEE 754 de punto flotante binario, que no representa exactamente muchos decimales",
            "Porque el redondeo depende del sistema operativo",
            "Porque 0.1 y 0.2 no son números válidos"
          ],
          "correcta": 1,
          "exp": "El punto flotante binario (IEEE 754) no puede representar exactamente fracciones decimales como 0.1. El comportamiento es transferible porque el estándar de hardware es el mismo, no un bug de un lenguaje concreto."
        },
        {
          "q": "En Rust y Java `Option<T>`/`Optional<T>` obligan a manejar la ausencia de valor, mientras que en muchos lenguajes cualquier referencia puede ser `null`. ¿Qué ventaja de seguridad aporta el enfoque de tipo opción?",
          "opciones": [
            "Hace el programa más rápido en tiempo de ejecución",
            "Convierte la posible ausencia en parte del tipo, forzando al compilador a exigir que se maneje antes de usar el valor",
            "Elimina la necesidad de escribir condicionales",
            "Permite que el valor sea de cualquier tipo a la vez"
          ],
          "correcta": 1,
          "exp": "El tipo opción hace explícita la nulabilidad en el sistema de tipos, así el compilador rechaza usar el valor sin desenvolverlo. Esto evita la clase de errores del \"null que se cuela\" (el \"error de los mil millones\")."
        },
        {
          "q": "Un `int` de 32 bits con signo desborda al superar ~2 mil millones. En C el resultado es indefinido, en Java \"da la vuelta\" (wrap), en Python el entero crece sin límite. Al portar un algoritmo entre ellos, ¿qué debes vigilar?",
          "opciones": [
            "Nada, todos los lenguajes tratan los enteros igual",
            "Que la semántica de desbordamiento difiere: precisión arbitraria vs. wrap-around vs. comportamiento indefinido pueden cambiar el resultado",
            "Solo el nombre del tipo, no su comportamiento",
            "Que Python es más lento, y nada más"
          ],
          "correcta": 1,
          "exp": "El tamaño y la política ante desbordamiento son decisiones semánticas: Python usa enteros de precisión arbitraria, Java define wrap-around, C deja el desbordamiento con signo como indefinido. Ignorarlo produce bugs sutiles al portar."
        },
        {
          "q": "En Rust escribes `let x = 5;` y en Go `x := 5` sin anotar el tipo, igual que en Python. ¿Significa eso que los tres tienen tipado dinámico?",
          "opciones": [
            "Sí: si no se escribe el tipo, el tipado es dinámico",
            "Sí, pero solo dentro de funciones",
            "No: Rust y Go infieren el tipo en compilación y queda fijo; Python asocia el tipo al valor en ejecución. La inferencia ahorra escritura, no elimina la verificación estática",
            "No, porque Python tampoco tiene tipos"
          ],
          "correcta": 2,
          "exp": "Inferencia y dinamismo son ejes distintos. El compilador de Rust deduce que `x` es `i32` y rechazará asignarle una cadena; en Python el nombre puede apuntar después a cualquier tipo. La ausencia de anotación no dice nada sobre cuándo se comprueban los tipos."
        },
        {
          "q": "La longitud de la cadena \"ñ\" o de un emoji difiere entre Python, Java, Go y Rust. ¿Qué explica esas discrepancias?",
          "opciones": [
            "Cada lenguaje mide una unidad distinta: bytes de la codificación (UTF-8 en Go y Rust), unidades de código UTF-16 (Java, JS) o puntos de código (Python 3), y ninguna coincide con los caracteres percibidos",
            "Algunos lenguajes tienen un bug en su función de longitud",
            "La diferencia depende del idioma del sistema operativo",
            "Solo las cadenas con acentos se comportan distinto; los emojis son siempre de longitud 1"
          ],
          "correcta": 0,
          "exp": "Una cadena es una secuencia sobre alguna unidad, y cada lenguaje eligió la suya. Además, ni el punto de código equivale al carácter visible: un grafema puede componerse de varios (emojis con modificadores). Por eso indexar cadenas por posición es frágil al portar código."
        },
        {
          "q": "En JavaScript `const cfg = {a: 1}` impide reasignar `cfg` pero permite `cfg.a = 2`. ¿Qué distinción conceptual revela?",
          "opciones": [
            "Que `const` no tiene ningún efecto en JavaScript",
            "Que los objetos en JS son en realidad primitivos",
            "Que `const` solo funciona con números",
            "Que la constancia del enlace (el nombre no puede apuntar a otro valor) es distinta de la inmutabilidad del valor apuntado (el objeto sigue siendo mutable)"
          ],
          "correcta": 3,
          "exp": "Son dos propiedades independientes que muchos lenguajes tratan por separado. Para inmutabilidad real hace falta congelar la estructura o usar tipos inmutables; confundir ambos conceptos hace creer que un dato está protegido cuando cualquier referencia puede mutarlo."
        },
        {
          "q": "Asignar un `long` a un `int` o un `double` a un `float` requiere una conversión explícita en Java y C#, pero al revés no. ¿Qué regla de diseño hay detrás?",
          "opciones": [
            "Que las conversiones siempre son gratuitas en tiempo de ejecución",
            "Que las conversiones que pueden perder información (estrechamiento) se exigen explícitas para que el programador declare que asume la pérdida, mientras que las que la preservan (ampliación) pueden ser implícitas",
            "Que solo se puede convertir entre tipos con el mismo nombre",
            "Que el compilador prohíbe cualquier conversión entre tipos numéricos"
          ],
          "correcta": 1,
          "exp": "El criterio es la posible pérdida de información, no el tamaño en sí. Ampliar es seguro; estrechar puede truncar o desbordar, así que el lenguaje obliga a un cast que documenta la decisión. Los lenguajes con coerción débil hacen esto en silencio, y ahí nacen bugs difíciles de ver."
        }
      ]
    },
    {
      "idx": 4,
      "titulo": "Control del programa",
      "preguntas": [
        {
          "q": "En Python `if x:` es verdadero para 1 pero falso para 0, \"\" o []. En Java `if (x)` exige un `boolean` estricto. ¿Qué concepto explica esta diferencia?",
          "opciones": [
            "La velocidad de la CPU",
            "La \"veracidad\" (truthiness): algunos lenguajes definen qué valores no booleanos cuentan como verdaderos/falsos; otros solo aceptan booleanos",
            "Que Java no tiene condicionales",
            "Que Python convierte todo a entero"
          ],
          "correcta": 1,
          "exp": "La truthiness es una decisión semántica: lenguajes como Python o JS asignan valor de verdad a no-booleanos (0, cadenas vacías, listas vacías son falsy); Java y otros exigen un booleano. Portar código requiere hacer explícita la condición."
        },
        {
          "q": "El operador `&&` en `a() && b()` no evalúa `b()` si `a()` es falso. ¿Por qué este \"cortocircuito\" es una decisión semántica y no solo una optimización?",
          "opciones": [
            "Porque acelera el programa sin cambiar nada más",
            "Porque permite usar la primera condición como guarda de la segunda (p. ej. `p != null && p.valor > 0`), de modo que evaluar b() o no cambia el comportamiento observable",
            "Porque && y & son siempre intercambiables",
            "Porque el compilador reordena las condiciones"
          ],
          "correcta": 1,
          "exp": "El cortocircuito garantiza que el segundo operando no se evalúa si el primero decide el resultado. Esto se usa como guarda para evitar efectos secundarios o errores (acceder a un nulo), por eso es semántico y no mera optimización."
        },
        {
          "q": "Rust y otros lenguajes exigen que un `match` cubra todos los casos (exhaustividad), mientras que un `switch` clásico en C no lo obliga. ¿Qué ventaja de mantenibilidad aporta la exhaustividad?",
          "opciones": [
            "Reduce el tamaño del ejecutable",
            "Si más adelante se añade una variante nueva al tipo, el compilador señala todos los match que ya no la cubren",
            "Hace innecesarias las pruebas unitarias",
            "Permite omitir la rama por defecto sin consecuencias"
          ],
          "correcta": 1,
          "exp": "El chequeo de exhaustividad convierte al compilador en red de seguridad: al agregar un caso al tipo suma, falla la compilación en cada match incompleto. Esto previene el clásico caso olvidado de los switch abiertos."
        },
        {
          "q": "Go maneja errores devolviéndolos como valores (`v, err := f()`), mientras Java o Python lanzan excepciones. ¿Qué diferencia de flujo de control implica esto al portar código?",
          "opciones": [
            "Ninguna, err y try/catch son idénticos",
            "Con excepciones el error se propaga automáticamente hasta un manejador; con valores de error hay que comprobarlo y propagarlo explícitamente en cada llamada",
            "Go no puede reportar errores",
            "Las excepciones siempre terminan el programa"
          ],
          "correcta": 1,
          "exp": "Las excepciones desvían el control implícitamente hacia el catch más cercano; los errores como valor obligan a chequear y propagar en cada punto. Portar entre ambos modelos reestructura el flujo, no solo la sintaxis."
        },
        {
          "q": "Eliminar elementos de una lista mientras se itera sobre ella provoca elementos saltados en unos lenguajes y una excepción de modificación concurrente en otros. ¿Qué principio transferible se deduce?",
          "opciones": [
            "Que las listas no deben usarse en bucles",
            "Que el problema desaparece si el bucle se recorre al revés en cualquier lenguaje",
            "Que mutar una colección mientras un iterador la recorre invalida el estado del iterador; lo robusto es construir una colección nueva o recolectar los índices y aplicar los cambios después",
            "Que solo ocurre en lenguajes con recolector de basura"
          ],
          "correcta": 2,
          "exp": "El iterador mantiene una posición sobre una estructura que asume estable. Unos lenguajes lo detectan y fallan rápido, otros producen resultados silenciosamente incorrectos: la variante que falla es la amable. La solución idiomática (filtrar hacia una colección nueva) funciona en todos."
        },
        {
          "q": "Una función recursiva de cola se convierte en un bucle en Scheme o Scala, pero desborda la pila en Python o Java aunque esté escrita igual. ¿Qué lo explica?",
          "opciones": [
            "La optimización de llamada de cola es una garantía del lenguaje o de su implementación, no una propiedad del código: si no la ofrece, cada llamada sigue apilando un marco",
            "Python y Java no permiten la recursión",
            "El código escrito así es incorrecto y por eso falla",
            "La diferencia depende únicamente de la memoria RAM disponible"
          ],
          "correcta": 0,
          "exp": "Escribir la recursión en posición de cola es condición necesaria pero no suficiente: alguien debe transformarla. Algunos lenguajes lo garantizan por especificación; otros lo rechazan explícitamente para conservar la traza de llamadas. Portar recursión profunda exige comprobarlo."
        },
        {
          "q": "`try/finally` en Java, `defer` en Go, `with` en Python y los destructores RAII en C++ y Rust resuelven el mismo problema. ¿Cuál es?",
          "opciones": [
            "Convertir errores en valores de retorno",
            "Acelerar la liberación de memoria del recolector de basura",
            "Impedir que el programa lance excepciones",
            "Garantizar que un recurso adquirido (archivo, conexión, bloqueo) se libere pase lo que pase, incluso si se sale del bloque por un error o un retorno anticipado"
          ],
          "correcta": 3,
          "exp": "El riesgo es la ruta de salida no prevista: un return temprano o una excepción se saltan el `close()` escrito al final. Cada lenguaje ata la liberación al ámbito por un mecanismo distinto, pero la garantía buscada es idéntica y por eso el concepto se transfiere."
        },
        {
          "q": "Capturar `catch (Exception e)` o `except Exception:` de forma genérica y continuar se considera mala práctica en todos los lenguajes con excepciones. ¿Por qué?",
          "opciones": [
            "Porque las excepciones genéricas son más lentas de capturar",
            "Porque atrapa también errores que no sabes manejar (fallos de programación, interrupciones, errores de recursos), oculta la causa real y deja el programa en un estado inconsistente que fallará más tarde y más lejos",
            "Porque el compilador lo prohíbe",
            "Porque solo se puede capturar una excepción por función"
          ],
          "correcta": 1,
          "exp": "Manejar un error significa saber qué hacer con él. Una captura genérica confunde lo esperable (archivo ausente) con lo imprevisto (un bug), y el síntoma reaparece desplazado, con la traza original perdida. Lo correcto es capturar el tipo concreto que sabes tratar."
        }
      ]
    },
    {
      "idx": 5,
      "titulo": "Funciones y modularidad",
      "preguntas": [
        {
          "q": "En Python `def f(x=[]):` con lista por defecto puede acumular datos entre llamadas, un error clásico. ¿Qué principio general sobre valores por defecto ilustra este caso?",
          "opciones": [
            "Que Python no admite parámetros por defecto",
            "Que si el valor por defecto es un objeto mutable evaluado una sola vez, puede compartirse entre llamadas; conviene usar un centinela inmutable (None) y crear el objeto dentro",
            "Que las listas no pueden pasarse a funciones",
            "Que el problema solo ocurre en lenguajes compilados"
          ],
          "correcta": 1,
          "exp": "El defecto mutable se crea una vez al definir la función y se reutiliza. El patrón transferible es usar un centinela inmutable (None) y construir el mutable dentro del cuerpo. Es una trampa de semántica de evaluación, no de sintaxis."
        },
        {
          "q": "Un closure captura variables de su entorno. En un bucle que crea funciones, capturar la variable del bucle \"por referencia\" en vez de \"por valor\" produce que todas las funciones vean el último valor. ¿Qué explica este comportamiento?",
          "opciones": [
            "Que los closures no pueden usarse en bucles",
            "Que el closure captura el enlace a la variable, no una copia de su valor en el momento de crear la función; todas comparten el mismo enlace mutado",
            "Que el bucle se ejecuta al revés",
            "Que las funciones anónimas no tienen alcance"
          ],
          "correcta": 1,
          "exp": "Muchos lenguajes capturan la variable (el enlace), no una instantánea de su valor. Al terminar el bucle todas las funciones ven el estado final. La solución es capturar una copia por iteración, una diferencia semántica clave entre lenguajes."
        },
        {
          "q": "En Java pasar un objeto a un método permite mutar sus campos, pero reasignar el parámetro no afecta al exterior. ¿Cómo se describe correctamente esta semántica?",
          "opciones": [
            "Paso por referencia puro",
            "Paso por valor de una referencia: se copia la referencia (por eso ves el mismo objeto), pero reasignar el parámetro no cambia la variable original",
            "Paso por nombre",
            "Paso por copia profunda del objeto"
          ],
          "correcta": 1,
          "exp": "Java (como Python o JS) pasa el valor de la referencia: ambos apuntan al mismo objeto, así que mutarlo se ve fuera, pero reasignar la variable local no. Confundir esto con paso por referencia verdadero causa errores al portar código."
        },
        {
          "q": "Los genéricos (`List<T>`, funciones parametrizadas) aparecen en Java, C#, Rust, TypeScript y Go. ¿Qué problema de diseño resuelven de forma transferible?",
          "opciones": [
            "Hacer el programa más rápido eliminando la memoria",
            "Escribir código que funciona con muchos tipos manteniendo la seguridad de tipos, sin duplicar la lógica ni renunciar a la verificación estática",
            "Permitir que cualquier función acepte cualquier número de argumentos",
            "Convertir un lenguaje estático en dinámico"
          ],
          "correcta": 1,
          "exp": "El polimorfismo paramétrico permite una sola implementación reutilizable para múltiples tipos conservando el chequeo estático. Evita la duplicación y la pérdida de seguridad que implicaría usar un tipo \"cualquiera\" sin tipar."
        },
        {
          "q": "Un bucle que acumula, otro que descarta elementos y otro que transforma se expresan en muchos lenguajes como `reduce`, `filter` y `map`. ¿Qué se gana al reconocer esos tres patrones?",
          "opciones": [
            "Siempre se ejecutan más rápido que un bucle explícito",
            "Se elimina la necesidad de entender los bucles",
            "Se nombra la intención de cada recorrido en lugar de dejarla implícita en la mecánica del bucle, lo que hace el código más legible y directamente traducible a cualquier lenguaje que ofrezca esas operaciones",
            "Permiten recorrer colecciones sin costo computacional"
          ],
          "correcta": 2,
          "exp": "Son abstracciones sobre la forma del recorrido: transformar, seleccionar, plegar. Al identificarlas, el lector sabe qué hace el bucle sin simularlo mentalmente, y la traducción entre lenguajes se vuelve mecánica porque el patrón, no la sintaxis, es lo que se transfiere."
        },
        {
          "q": "Java y C# permiten varias funciones con el mismo nombre y distinta firma; Python no, pero ofrece argumentos por defecto y con nombre. ¿Qué compensación describe esto?",
          "opciones": [
            "La sobrecarga resuelve en compilación cuál función llamar según los tipos estáticos de los argumentos; los argumentos por defecto y con nombre logran flexibilidad similar con una sola función, decidiendo en ejecución",
            "Son exactamente lo mismo con nombres distintos",
            "Los argumentos con nombre solo existen en lenguajes compilados",
            "La sobrecarga hace innecesario declarar los tipos"
          ],
          "correcta": 0,
          "exp": "La sobrecarga necesita tipos estáticos para elegir la variante y por eso encaja mal en lenguajes dinámicos. Los parámetros opcionales y con nombre cubren el mismo caso de uso con un único punto de entrada, lo que suele dar mejor documentación y menos ambigüedad."
        },
        {
          "q": "Go exporta un identificador si empieza con mayúscula, Java usa `public`/`private`, Python solo tiene la convención del guion bajo inicial. ¿Qué concepto común hay detrás?",
          "opciones": [
            "El estilo de nombres del equipo, sin consecuencia técnica",
            "La velocidad de acceso a los miembros del módulo",
            "La obligación de escribir documentación para cada símbolo",
            "El control de visibilidad: separar la interfaz pública (contrato estable con el exterior) de los detalles internos que pueden cambiar sin romper a nadie"
          ],
          "correcta": 3,
          "exp": "El objetivo es delimitar qué se compromete a mantener el módulo. Lo interesante es el grado de aplicación: unos lenguajes lo imponen en compilación y Python lo deja como convención, confiando en el programador. El principio de diseño es el mismo; la garantía, no."
        },
        {
          "q": "Pasar una función como argumento es natural en Python, JS, Rust o Go, y en C se hace con punteros a función. ¿Qué significa que las funciones sean \"de primera clase\"?",
          "opciones": [
            "Que se ejecutan antes que el resto del programa",
            "Que pueden tratarse como cualquier otro valor: asignarse a variables, pasarse como argumento, devolverse y almacenarse en estructuras de datos",
            "Que están escritas en la biblioteca estándar del lenguaje",
            "Que solo pueden definirse en el nivel superior del archivo"
          ],
          "correcta": 1,
          "exp": "Ser de primera clase es no tener restricciones respecto a otros valores. El puntero a función de C cubre parte del caso, pero sin capturar el entorno: la diferencia con un closure real es que este arrastra su contexto, lo que habilita callbacks y currificación."
        }
      ]
    },
    {
      "idx": 6,
      "titulo": "Datos y estructuras",
      "preguntas": [
        {
          "q": "En Python `a = [1,2]; b = a` y luego `b.append(3)` cambia también `a`. ¿Qué distinción conceptual explica esto y cómo se evita?",
          "opciones": [
            "Igualdad vs. identidad no aplica a listas",
            "`b = a` copia la referencia (identidad compartida), no el contenido; para independizarlos hace falta una copia (superficial o profunda)",
            "Las listas de Python son inmutables",
            "append crea una lista nueva cada vez"
          ],
          "correcta": 1,
          "exp": "Asignar copia la referencia: ambos nombres apuntan al mismo objeto. Mutar por uno se ve por el otro. Para separarlos se necesita copia superficial o profunda. Es la distinción referencia/valor e identidad/igualdad, común a muchos lenguajes."
        },
        {
          "q": "Comparar dos objetos con `==` puede probar igualdad de contenido o identidad de referencia según el lenguaje (Java `==` vs `.equals()`, Python `==` vs `is`). ¿Por qué importa esta distinción?",
          "opciones": [
            "Porque la identidad y la igualdad siempre coinciden",
            "Porque dos objetos pueden ser iguales en contenido pero ser instancias distintas; usar el operador equivocado produce comparaciones que fallan sutilmente",
            "Porque == solo funciona con números",
            "Porque .equals() es más lento y por eso se evita"
          ],
          "correcta": 1,
          "exp": "Identidad (¿son el mismo objeto en memoria?) e igualdad (¿tienen el mismo valor?) son cosas distintas. Java `==` compara referencias para objetos; hay que usar `equals`. Confundirlas es una fuente clásica de bugs transferible entre lenguajes."
        },
        {
          "q": "Un tipo algebraico de suma (enum con datos en Rust, `sealed`/uniones etiquetadas) modela \"una de varias formas posibles\". ¿Qué ventaja tiene sobre representar el mismo caso con un entero o cadena de estado?",
          "opciones": [
            "Ocupa menos memoria siempre",
            "Cada variante puede llevar sus propios datos y el compilador verifica que se manejen todas, haciendo imposible representar estados inválidos",
            "Permite valores nulos en cualquier campo",
            "Elimina la necesidad de estructuras de datos"
          ],
          "correcta": 1,
          "exp": "Los tipos suma hacen que los estados inválidos sean irrepresentables y, combinados con match exhaustivo, obligan a cubrir cada caso. Un entero o cadena de estado permite valores fuera de rango y no lleva datos asociados por variante."
        },
        {
          "q": "Una tabla hash (dict, map, HashMap) ofrece acceso promedio O(1). Al portar código que asume orden de inserción, ¿qué debes verificar entre lenguajes?",
          "opciones": [
            "Que todos garantizan el mismo orden de iteración",
            "Que la garantía de orden varía: algunos preservan orden de inserción, otros no dan ninguna garantía; no se debe asumir un orden si el lenguaje no lo promete",
            "Que las tablas hash no permiten iterar",
            "Que el rendimiento es O(n) en todos"
          ],
          "correcta": 1,
          "exp": "El orden de iteración de un mapa es una garantía específica de cada lenguaje/versión (Python 3.7+ preserva inserción; el map de Go no la garantiza y aleatoriza). Asumir orden portando código produce fallos no deterministas."
        },
        {
          "q": "Añadir elementos a un vector dinámico es O(1) amortizado aunque a veces obligue a copiar todo el contenido. ¿Qué estrategia lo consigue y por qué importa al portar código?",
          "opciones": [
            "Duplicar (o multiplicar por un factor) la capacidad al llenarse, de modo que las copias caras se reparten entre muchas inserciones baratas; el coste puntual sigue existiendo y puede invalidar referencias al contenido anterior",
            "Reservar de antemano memoria infinita para el vector",
            "Insertar siempre al principio en lugar de al final",
            "Convertir el vector en una lista enlazada cuando se llena"
          ],
          "correcta": 0,
          "exp": "El crecimiento geométrico amortiza el coste, pero la reasignación mueve los datos: en C++ invalida iteradores y en Rust el verificador de préstamos impide guardar una referencia mientras se inserta. Un arreglo de tamaño fijo no tiene ese coste ni ese riesgo, a cambio de no crecer."
        },
        {
          "q": "Serializas una estructura a JSON en Python y la lees en Go; una tupla llega como lista, una fecha como cadena y un entero muy grande pierde precisión al pasar por JavaScript. ¿Qué explica estas pérdidas?",
          "opciones": [
            "Que la biblioteca de JSON del lenguaje está mal implementada",
            "Que JSON solo admite datos numéricos",
            "Que JSON tiene un conjunto de tipos propio y reducido (objeto, arreglo, cadena, número, booleano, nulo): todo lo que no encaje se proyecta sobre él y el receptor debe reconstruir el significado según un contrato acordado",
            "Que la codificación UTF-8 no soporta números grandes"
          ],
          "correcta": 2,
          "exp": "Serializar es proyectar el modelo de tipos del lenguaje sobre el del formato, y esa proyección no siempre es reversible. Además, JSON no fija la precisión numérica y muchos lectores usan doble flotante. Por eso los contratos definen explícitamente formatos de fecha e identificadores como cadenas."
        },
        {
          "q": "Un conjunto (set) comprueba la pertenencia en O(1) promedio, pero exige que sus elementos sean hashables o inmutables. ¿Por qué esa restricción?",
          "opciones": [
            "Para ahorrar memoria en la estructura interna",
            "Porque los conjuntos solo pueden guardar números y cadenas",
            "Porque los elementos deben poder ordenarse alfabéticamente",
            "Porque la posición del elemento se deriva de su hash: si el elemento muta después de insertarse, su hash cambia y queda almacenado en un lugar donde ya no se le encontrará"
          ],
          "correcta": 3,
          "exp": "El acceso rápido depende de que hash e igualdad sean estables durante la vida del elemento en la estructura. Mutar una clave la vuelve invisible sin lanzar error. Es el mismo motivo por el que las claves de un diccionario deben ser inmutables en cualquier lenguaje."
        }
      ]
    },
    {
      "idx": 7,
      "titulo": "Paradigmas",
      "preguntas": [
        {
          "q": "La herencia de implementación y la composición son dos formas de reutilizar código en OO. ¿Por qué la máxima \"preferir composición sobre herencia\" es transferible entre lenguajes?",
          "opciones": [
            "Porque la herencia no existe en lenguajes modernos",
            "Porque la herencia acopla fuertemente subclase y superclase (jerarquía rígida, fragilidad ante cambios), mientras la composición delega en piezas intercambiables con menor acoplamiento",
            "Porque la composición siempre es más rápida en ejecución",
            "Porque los lenguajes prohíben más de un nivel de herencia"
          ],
          "correcta": 1,
          "exp": "La herencia crea acoplamiento fuerte y jerarquías frágiles (el \"problema de la clase base frágil\"); la composición ensambla comportamientos mediante objetos delegados intercambiables. El principio guía el diseño en cualquier lenguaje OO."
        },
        {
          "q": "El OO de JavaScript se basa en prototipos, no en clases al estilo de Java (aunque `class` es azúcar sintáctico). ¿Qué diferencia semántica real hay bajo esa sintaxis?",
          "opciones": [
            "Ninguna, class en JS crea clases idénticas a las de Java",
            "Los objetos delegan en otro objeto (su prototipo) en tiempo de ejecución; la \"herencia\" es una cadena de delegación mutable, no una plantilla estática de clase",
            "JavaScript no permite crear objetos",
            "Los prototipos solo existen en el navegador"
          ],
          "correcta": 1,
          "exp": "En JS un objeto delega búsquedas de propiedades a su prototipo por una cadena que puede modificarse en ejecución. La palabra `class` es azúcar sobre este modelo; entender la delegación explica comportamientos que el modelo de clases no predice."
        },
        {
          "q": "La programación funcional insiste en la inmutabilidad y las funciones puras. ¿Qué beneficio concreto tiene esto para la concurrencia?",
          "opciones": [
            "Hace innecesario cualquier bloqueo porque impide ejecutar en paralelo",
            "Si los datos no mutan y las funciones no tienen efectos, no hay estado compartido que corromper, eliminando muchas condiciones de carrera",
            "Convierte automáticamente el código en asíncrono",
            "Elimina la necesidad de estructuras de datos"
          ],
          "correcta": 1,
          "exp": "Las condiciones de carrera nacen de estado mutable compartido. Datos inmutables y funciones puras (sin efectos) eliminan esa raíz, permitiendo compartir datos entre hilos sin sincronización. Es una ventaja transferible del paradigma funcional."
        },
        {
          "q": "SQL es declarativo: escribes `SELECT ... WHERE ...` sin decir cómo recorrer las filas. ¿Qué implica esto respecto a quién decide la estrategia de ejecución?",
          "opciones": [
            "El programador debe escribir el bucle de recorrido manualmente",
            "El motor (planificador de consultas) elige el algoritmo y el orden de acceso; el programador especifica el resultado deseado, no el procedimiento",
            "SQL ejecuta siempre en el mismo orden fila por fila",
            "La estrategia la fija el sistema operativo"
          ],
          "correcta": 1,
          "exp": "En el paradigma declarativo el programador expresa el QUÉ y el optimizador de consultas decide el CÓMO (índices, joins, orden). Esta separación es la esencia de lo declarativo y contrasta con el control explícito del código imperativo."
        },
        {
          "q": "En Java un tipo declara `implements Comparable`; en Go basta con que tenga los métodos de la interfaz, sin mencionarla. ¿Qué consecuencia de diseño tiene la satisfacción implícita?",
          "opciones": [
            "Que Go no verifica los tipos en compilación",
            "Que se puede definir una interfaz a posteriori que tipos ya escritos (incluso de bibliotecas ajenas) satisfagan sin modificarlos, moviendo la definición del contrato al consumidor y no al proveedor",
            "Que las interfaces de Go solo funcionan con structs propios",
            "Que Go no permite polimorfismo"
          ],
          "correcta": 1,
          "exp": "Es tipado estructural verificado estáticamente: el compilador comprueba la forma, no una declaración de intención. Esto desacopla paquetes (quien usa define la interfaz mínima que necesita), a cambio de perder la declaración explícita que documenta el contrato en el tipo."
        },
        {
          "q": "En Java todos los métodos de instancia son virtuales por defecto; en C++ solo lo son los marcados `virtual`. ¿Qué diferencia produce esto en tiempo de ejecución?",
          "opciones": [
            "Sin `virtual`, C++ resuelve la llamada según el tipo estático de la referencia en compilación; con `virtual` (y en Java siempre) se consulta una tabla de despacho y se ejecuta la implementación del tipo real del objeto",
            "Ninguna: ambos lenguajes eligen siempre el método de la subclase",
            "Java es más lento porque no usa tablas de métodos",
            "C++ decide en ejecución y Java en compilación"
          ],
          "correcta": 0,
          "exp": "Es la diferencia entre despacho estático y dinámico. En C++ llamar a un método no virtual desde una referencia a la clase base ejecuta la versión de la base aunque el objeto sea de la derivada, un resultado que sorprende a quien viene de Java. El polimorfismo tiene un coste que C++ deja opcional."
        },
        {
          "q": "Python, Rust, JavaScript y C# permiten programar con objetos, con funciones de orden superior y de forma imperativa. ¿Qué implica que los lenguajes sean multiparadigma?",
          "opciones": [
            "Que ninguno de esos lenguajes tiene un diseño coherente",
            "Que el paradigma lo elige el compilador según el rendimiento",
            "Que el paradigma es una forma de estructurar la solución, no una etiqueta del lenguaje: se elige por problema (y a menudo por módulo), y lo que varía entre lenguajes es cuánto apoya e incentiva cada estilo",
            "Que todos los paradigmas producen exactamente el mismo código máquina"
          ],
          "correcta": 2,
          "exp": "Los paradigmas describen maneras de descomponer un problema y conviven en un mismo programa: un núcleo funcional puro, una capa de objetos para el dominio, código imperativo en los bordes. La pregunta útil no es de qué paradigma es el lenguaje, sino cuál conviene aquí y qué facilita el lenguaje."
        }
      ]
    },
    {
      "idx": 8,
      "titulo": "Cómo funcionan los lenguajes",
      "preguntas": [
        {
          "q": "Java y C# compilan a bytecode que corre sobre una VM (JVM/CLR) con JIT, mientras C compila directo a código máquina (AOT). ¿Qué compensación fundamental describe esto?",
          "opciones": [
            "El bytecode siempre es más lento y no tiene ventajas",
            "La VM con JIT gana portabilidad y optimización en caliente en tiempo de ejecución a costa de arranque y de un runtime; el AOT arranca rápido y sin runtime pero es específico de plataforma",
            "AOT y JIT producen exactamente el mismo binario",
            "El JIT solo sirve para interpretar, nunca compila"
          ],
          "correcta": 1,
          "exp": "El JIT compila en ejecución usando información real de uso (portabilidad y optimización dinámica) pero paga arranque y memoria del runtime. El AOT genera un binario nativo veloz al iniciar y sin VM, a cambio de atarse a una plataforma."
        },
        {
          "q": "Rust evita el recolector de basura con propiedad y préstamos verificados en compilación; Java y Go usan GC. ¿Qué compensación de diseño representa cada enfoque?",
          "opciones": [
            "El GC siempre produce programas incorrectos",
            "El modelo de propiedad da control y predictibilidad de memoria sin pausas de GC, a costa de una disciplina que el compilador exige; el GC simplifica al programador a costa de pausas y sobrecarga en ejecución",
            "Rust y el GC son idénticos en rendimiento y ergonomía",
            "El GC solo existe en lenguajes interpretados"
          ],
          "correcta": 1,
          "exp": "Propiedad/préstamos mueven la gestión de memoria a tiempo de compilación: sin GC, sin pausas, pero con reglas estrictas que el programador debe satisfacer. El GC libera al programador de esa carga a cambio de sobrecarga y pausas en ejecución."
        },
        {
          "q": "Una recursión muy profunda provoca \"stack overflow\" en muchos lenguajes. ¿Qué explica este límite y por qué el heap no lo tiene igual?",
          "opciones": [
            "El stack es infinito pero el programa se cansa",
            "Cada llamada apila un marco (frame) en una pila de tamaño acotado; agotar ese espacio desborda la pila, mientras el heap crece dinámicamente y se limita por la memoria disponible",
            "El heap y el stack son la misma región",
            "El overflow ocurre solo en lenguajes sin funciones"
          ],
          "correcta": 1,
          "exp": "Cada invocación reserva un marco de llamada en la pila, de tamaño fijo y acotado. La recursión sin caso base o sin optimización de cola agota ese espacio. El heap se asigna dinámicamente y se limita por la memoria total, no por un tope de pila."
        },
        {
          "q": "Una condición de carrera aparece cuando dos hilos acceden a memoria compartida sin sincronización. ¿Por qué el modelo de actores (Erlang/Elixir) las evita por diseño?",
          "opciones": [
            "Porque los actores usan un solo hilo siempre",
            "Porque cada actor tiene estado privado y solo se comunica por mensajes; al no haber memoria compartida mutable, no existe la carrera sobre ese estado",
            "Porque los mensajes son más rápidos que la memoria",
            "Porque el GC elimina las carreras automáticamente"
          ],
          "correcta": 1,
          "exp": "El modelo de actores aísla el estado en cada proceso y prohíbe el acceso directo desde fuera: la única interacción es por mensajes. Sin estado mutable compartido desaparece la raíz de las condiciones de carrera."
        },
        {
          "q": "Un programa en Python con cuatro hilos que hacen cálculo puro no va más rápido que con uno, pero cuatro procesos sí. ¿Qué explica esta asimetría?",
          "opciones": [
            "Que los hilos siempre son más lentos que los procesos en cualquier lenguaje",
            "Que el cálculo puro no se puede paralelizar",
            "Que Python no permite crear hilos",
            "Que el bloqueo global del intérprete (GIL) de CPython permite ejecutar bytecode en un solo hilo a la vez; los procesos tienen intérpretes separados y sí usan varios núcleos"
          ],
          "correcta": 3,
          "exp": "Es una decisión de la implementación, no del lenguaje: simplifica la gestión de memoria del intérprete a costa del paralelismo de CPU. Los hilos siguen sirviendo para E/S, donde el bloqueo se libera al esperar. Al portar código concurrente hay que preguntar siempre qué hace el runtime por debajo."
        },
        {
          "q": "Un servidor `async` de un solo hilo atiende miles de conexiones simultáneas, pero una función que calcula durante dos segundos bloquea a todos los clientes. ¿Por qué?",
          "opciones": [
            "Porque async convierte el programa en secuencial al detectar cálculos",
            "Porque async/await es concurrencia cooperativa sobre un bucle de eventos: las tareas ceden el control solo en los puntos de espera, así que una tarea que no espera nunca devuelve el control al planificador",
            "Porque el sistema operativo limita el número de conexiones",
            "Porque las funciones asíncronas no pueden hacer cálculos"
          ],
          "correcta": 1,
          "exp": "Concurrencia no es paralelismo: el bucle de eventos entrelaza tareas que pasan la mayor parte del tiempo esperando E/S. Sin puntos de suspensión no hay entrelazado. La solución transferible es mover el trabajo intensivo de CPU a un hilo o proceso aparte."
        },
        {
          "q": "Antes de ejecutar una sola línea, un intérprete convierte el texto fuente en tokens, luego en un árbol de sintaxis abstracta y a menudo en bytecode. ¿Por qué existen esas etapas intermedias?",
          "opciones": [
            "Porque cada etapa impone una estructura que la siguiente necesita: los tokens agrupan caracteres en unidades léxicas, el árbol captura la estructura y la precedencia, y el bytecode ofrece una forma compacta y ya analizada que se ejecuta muchas veces sin volver a analizar el texto",
            "Porque el código fuente debe cifrarse antes de ejecutarse",
            "Porque son un requisito del sistema operativo",
            "Porque así el programa ocupa menos espacio en disco"
          ],
          "correcta": 0,
          "exp": "Cada fase reduce ambigüedad y prepara la siguiente. El bytecode además amortiza el análisis: dentro de un bucle no se vuelve a parsear nada. Comprender el pipeline explica por qué un error de sintaxis aparece antes de ejecutar y un error de nombre solo al llegar a la línea."
        }
      ]
    },
    {
      "idx": 9,
      "titulo": "Ingeniería de software políglota",
      "preguntas": [
        {
          "q": "Un lockfile (package-lock.json, Cargo.lock, poetry.lock) acompaña al manifiesto de dependencias. ¿Qué garantiza y por qué es transferible entre ecosistemas?",
          "opciones": [
            "Que las dependencias se descarguen más rápido",
            "Que se fijen las versiones exactas de todo el árbol de dependencias, haciendo la instalación reproducible en cualquier máquina y momento",
            "Que solo se use la última versión disponible siempre",
            "Que las dependencias no necesiten internet"
          ],
          "correcta": 1,
          "exp": "El manifiesto declara rangos deseados; el lockfile congela las versiones resueltas de todo el árbol. Así cualquier colaborador o CI instala exactamente lo mismo. El concepto de compilación reproducible se repite en todos los ecosistemas."
        },
        {
          "q": "El patrón Singleton, Observer o Strategy se puede implementar en Java, Python o Go de formas muy distintas. ¿Qué revela esto sobre los patrones de diseño?",
          "opciones": [
            "Que los patrones son código copiable idéntico entre lenguajes",
            "Que un patrón es una solución conceptual a un problema recurrente; su forma depende de los recursos del lenguaje (funciones de primera clase, interfaces, closures) y a veces el lenguaje lo hace innecesario",
            "Que solo Java tiene patrones de diseño",
            "Que los patrones no aplican a lenguajes dinámicos"
          ],
          "correcta": 1,
          "exp": "Los patrones son soluciones a problemas de diseño recurrentes, no recetas de código fijo. Con funciones de primera clase, por ejemplo, Strategy suele reducirse a pasar una función; el patrón se adapta a lo que ofrece cada lenguaje."
        },
        {
          "q": "Antes de refactorizar código legado, la práctica recomendada es tener pruebas que cubran su comportamiento actual. ¿Por qué esta regla es independiente del lenguaje?",
          "opciones": [
            "Porque las pruebas hacen el código más rápido",
            "Porque refactorizar significa cambiar la estructura interna sin cambiar el comportamiento observable; las pruebas son la red que detecta si se rompe ese comportamiento",
            "Porque sin pruebas el código no compila",
            "Porque solo se puede refactorizar código con pruebas en Java"
          ],
          "correcta": 1,
          "exp": "Por definición la refactorización preserva el comportamiento externo mientras mejora la estructura. Las pruebas verifican esa preservación tras cada paso. El principio aplica igual en cualquier lenguaje porque describe qué es refactorizar."
        },
        {
          "q": "El verificador de equivalencia del programa corre la misma entrada por implementaciones en varios lenguajes y compara salidas. ¿Qué tipo de prueba es y qué valida?",
          "opciones": [
            "Una prueba unitaria de una sola función interna",
            "Una prueba de integración/equivalencia: valida que implementaciones distintas producen la misma salida observable para la misma entrada, confirmando que son traducciones fieles",
            "Una prueba de rendimiento que mide velocidad",
            "Una prueba de seguridad de dependencias"
          ],
          "correcta": 1,
          "exp": "Compara el comportamiento externo de múltiples implementaciones ante entradas comunes: es una prueba de equivalencia (de integración de salidas). Confirma que las versiones en distintos lenguajes son equivalentes en resultado, no memoriza sintaxis."
        },
        {
          "q": "En producción se recomienda logging estructurado con niveles en lugar de imprimir por salida estándar. ¿Qué se gana?",
          "opciones": [
            "Que el programa se ejecute más rápido",
            "Que los mensajes dejen de escribirse en disco",
            "Control del destino y del volumen sin tocar el código (filtrar por nivel, enviar a distintos destinos) y registros con contexto y formato consistente que las herramientas pueden buscar, correlacionar y agregar",
            "Que los errores desaparezcan del programa"
          ],
          "correcta": 2,
          "exp": "Un `print` es un efecto fijo y sin metadatos. El logging separa la emisión del evento de la decisión de qué se conserva y dónde va, y añade marca de tiempo, nivel y contexto. En un sistema políglota eso permite correlacionar el rastro de una petición entre servicios de distintos lenguajes."
        },
        {
          "q": "La regla \"medir antes de optimizar\" se repite en todos los ecosistemas. ¿Cuál es su fundamento?",
          "opciones": [
            "Que la intuición sobre dónde se consume el tiempo suele fallar: el coste se concentra en pocos puntos, y sin un perfilador se optimiza código irrelevante añadiendo complejidad sin beneficio",
            "Que optimizar sin medir hace que el programa deje de compilar",
            "Que los perfiladores hacen el programa más rápido por sí mismos",
            "Que el rendimiento solo importa en lenguajes compilados"
          ],
          "correcta": 0,
          "exp": "El perfilado sustituye la conjetura por evidencia, e incluso cambia el diagnóstico: muchas veces el cuello de botella es E/S, una consulta sin índice o una asignación en un bucle, no el algoritmo sospechoso. Optimizar a ciegas cuesta legibilidad y suele no mover la aguja."
        },
        {
          "q": "Un proyecto políglota configura en CI una matriz que ejecuta el mismo flujo para cada lenguaje y cada versión soportada. ¿Qué garantiza ese diseño?",
          "opciones": [
            "Que el código se ejecute más rápido en producción",
            "Que solo haga falta probar el lenguaje principal del proyecto",
            "Que las dependencias se actualicen automáticamente",
            "Que cada combinación declarada como soportada se verifique de forma reproducible y aislada, detectando roturas específicas de una versión o un lenguaje antes de fusionar"
          ],
          "correcta": 3,
          "exp": "La matriz convierte la promesa de compatibilidad en algo comprobado en cada cambio, con trabajos independientes que aíslan el fallo. Es la versión automatizada del verificador de equivalencia: si soportas diez lenguajes, los diez deben pasar por la misma puerta."
        }
      ]
    },
    {
      "idx": 10,
      "titulo": "Interoperabilidad y fronteras entre lenguajes",
      "preguntas": [
        {
          "q": "Python (con ctypes/cffi), Rust, Go y Java pueden llamar a bibliotecas escritas en C. ¿Por qué C actúa como \"lengua franca\" de la interoperabilidad?",
          "opciones": [
            "Porque C es el lenguaje más rápido",
            "Porque su ABI y convenciones de llamada estables son el objetivo común que casi todos los runtimes saben invocar; la FFI se define contra esa interfaz de C",
            "Porque C se puede transpilar a cualquier lenguaje",
            "Porque todos los lenguajes están escritos en C"
          ],
          "correcta": 1,
          "exp": "La FFI de la mayoría de lenguajes se define contra la ABI de C, estable y ampliamente soportada. Por eso una biblioteca con interfaz C se puede llamar desde muchos runtimes: C es el mínimo común denominador binario, no el más rápido."
        },
        {
          "q": "Al enviar datos entre un servicio en Go y otro en Python, se elige JSON, Protobuf o MessagePack. ¿Qué compensación distingue a JSON de Protobuf?",
          "opciones": [
            "JSON es binario y Protobuf es texto",
            "JSON es texto legible y autodescriptivo pero más voluminoso y sin esquema forzado; Protobuf es binario compacto y rápido con un esquema explícito compartido, a costa de legibilidad y de generar código",
            "Protobuf no funciona entre lenguajes distintos",
            "Ambos son idénticos salvo el nombre"
          ],
          "correcta": 1,
          "exp": "JSON prioriza legibilidad y flexibilidad sin esquema; Protobuf prioriza tamaño y velocidad con un contrato de esquema compartido entre lenguajes, pagando legibilidad y un paso de generación. La elección depende del contexto de la frontera."
        },
        {
          "q": "Dos servicios se comunican por un contrato de API (REST u gRPC). ¿Por qué el contrato/esquema importa más que el lenguaje interno de cada servicio?",
          "opciones": [
            "Porque ambos servicios deben estar escritos en el mismo lenguaje",
            "Porque mientras respeten el contrato acordado (formatos, tipos, semántica), cada servicio puede implementarse y evolucionar en el lenguaje que convenga sin romper al otro",
            "Porque el contrato define el hardware necesario",
            "Porque REST y gRPC obligan a usar JSON siempre"
          ],
          "correcta": 1,
          "exp": "El contrato es la frontera estable: define qué se intercambia, no cómo se implementa. Respetarlo permite que cada componente use el lenguaje más adecuado y evolucione internamente sin afectar a los demás. Esa es la base de los sistemas políglotas."
        },
        {
          "q": "WebAssembly permite compilar Rust, C o Go a un objetivo común que corre en el navegador y en servidores. ¿Qué idea de interoperabilidad representa?",
          "opciones": [
            "Que Wasm reemplaza a todos los lenguajes existentes",
            "Un formato binario portátil y con espacio de memoria aislado (sandbox) como objetivo de compilación común, de modo que muchos lenguajes se ejecutan en el mismo host de forma segura",
            "Que solo JavaScript puede generar Wasm",
            "Que Wasm es un lenguaje de alto nivel que se escribe a mano"
          ],
          "correcta": 1,
          "exp": "Wasm es un objetivo de compilación portátil y aislado: distintos lenguajes compilan a él y corren en cualquier host que lo soporte, con un sandbox de seguridad. Es un punto de encuentro binario común, no un reemplazo de los lenguajes fuente."
        },
        {
          "q": "Actualizas una biblioteca compartida en C y un programa que la usaba deja de funcionar, aunque su código fuente no cambió y las firmas siguen siendo las mismas. ¿Qué se rompió?",
          "opciones": [
            "La API, porque los nombres de las funciones cambiaron",
            "La ABI: el contrato binario (disposición de las estructuras, tamaños, convención de llamada, símbolos) que la API a nivel de fuente no captura; el programa habría seguido funcionando si se recompilara contra la nueva versión",
            "El sistema operativo dejó de soportar el enlace dinámico",
            "El compilador del programa quedó obsoleto"
          ],
          "correcta": 1,
          "exp": "API y ABI son compatibilidades distintas: la primera vive en el código fuente, la segunda en el binario ya compilado. Añadir un campo a una struct pública mantiene la API pero desplaza los desplazamientos de memoria. Por eso el enlace dinámico exige versionar la ABI con cuidado."
        },
        {
          "q": "Dos componentes en lenguajes distintos se comunican lanzando uno al otro como subproceso e intercambiando líneas por entrada y salida estándar. ¿Qué virtud tiene esta frontera frente a una FFI?",
          "opciones": [
            "Es siempre más rápida que llamar a una función nativa",
            "Permite compartir estructuras de datos en memoria entre ambos lenguajes",
            "Aísla los procesos: cada uno tiene su propia memoria y su runtime, un fallo de uno no corrompe al otro, y el acoplamiento se reduce a un formato de texto que cualquier lenguaje sabe leer y escribir",
            "Elimina la necesidad de definir un formato de datos"
          ],
          "correcta": 2,
          "exp": "Cruzar por procesos cuesta latencia y serialización, pero compra aislamiento y simplicidad: no hay que compartir un modelo de memoria ni convivir con dos recolectores. La FFI es más rápida porque comparte espacio de direcciones, y por eso mismo un error de puntero derriba todo el proceso."
        },
        {
          "q": "Un binding que expone una biblioteca de C a Python resulta lento cuando se llama millones de veces en un bucle, pese a que la función en C es rapidísima. ¿Qué explica la sobrecarga?",
          "opciones": [
            "El coste de cruzar la frontera en cada llamada: convertir tipos entre ambas representaciones (marshalling), gestionar la propiedad de la memoria y el bloqueo del intérprete; por eso conviene mover el bucle al lado nativo y cruzar una sola vez con todos los datos",
            "Que el código en C se reinterpreta línea a línea desde Python",
            "Que la biblioteca en C se recompila en cada llamada",
            "Que Python traduce el código C a bytecode antes de ejecutarlo"
          ],
          "correcta": 0,
          "exp": "La conversión de representaciones y el manejo de propiedad se pagan por llamada, no por unidad de trabajo. El patrón transferible es hacer la frontera gruesa: pocas llamadas que transporten mucho (arreglos completos) en vez de muchas llamadas triviales."
        }
      ]
    },
    {
      "idx": 11,
      "titulo": "Proyecto integrador políglota",
      "preguntas": [
        {
          "q": "El proyecto usa un lenguaje de sistemas para una CLI, un backend para la API, JS/TS para el frontend y SQL para los datos. ¿Qué criterio justifica esta división en lugar de un solo lenguaje?",
          "opciones": [
            "Usar más lenguajes siempre es mejor",
            "Cada componente tiene requisitos distintos (rendimiento, ecosistema, modelo de ejecución) y se elige la herramienta cuyas fortalezas encajan, respetando contratos entre piezas",
            "El equipo quería practicar sintaxis variada",
            "Un solo lenguaje es incapaz de leer archivos"
          ],
          "correcta": 1,
          "exp": "La elección políglota se defiende por adecuación: el navegador impone JS/TS, las consultas encajan en SQL, un binario veloz sin runtime favorece un lenguaje de sistemas. Se justifica por fortalezas y contratos claros, no por variedad porque sí."
        },
        {
          "q": "Al diseñar los contratos entre componentes, ¿por qué conviene definir primero las responsabilidades y las interfaces antes de escribir cada componente?",
          "opciones": [
            "Porque así se elige el color del logo",
            "Porque contratos claros desacoplan a los equipos y permiten desarrollar, probar y desplegar cada componente de forma independiente sin conocer la implementación interna del otro",
            "Porque obliga a usar el mismo lenguaje en todos",
            "Porque el contrato define la velocidad de la CPU"
          ],
          "correcta": 1,
          "exp": "Definir responsabilidades e interfaces primero establece fronteras estables: cada componente cumple su contrato y puede evolucionar aislado. Esto habilita desarrollo paralelo, pruebas por componente y despliegue independiente."
        },
        {
          "q": "Las pruebas end-to-end del sistema completo se distinguen de las unitarias de cada componente. ¿Qué valida específicamente una prueba E2E en un sistema políglota?",
          "opciones": [
            "Que cada función interna devuelve el valor correcto",
            "Que las piezas en distintos lenguajes, al integrarse a través de sus contratos reales, producen el comportamiento esperado del sistema como un todo",
            "Que el código compila en cada lenguaje",
            "Que las dependencias no tienen vulnerabilidades"
          ],
          "correcta": 1,
          "exp": "La prueba E2E ejercita el sistema completo a través de sus fronteras reales (API, datos, frontend), verificando que la integración entre lenguajes funciona. Las unitarias validan piezas aisladas; la E2E valida el todo integrado."
        },
        {
          "q": "La retrospectiva final enfatiza \"transferir lo aprendido a un lenguaje nuevo no visto en el curso\". ¿Qué habilidad concreta se espera que el estudiante haya desarrollado?",
          "opciones": [
            "Memorizar la sintaxis de los diez lenguajes del núcleo",
            "Mapear un lenguaje desconocido a conceptos ya dominados (tipado, memoria, control, paradigma) para aprenderlo rápido preguntando qué cambia y por qué",
            "Rechazar cualquier lenguaje que no esté en el curso",
            "Reescribir todo el proyecto en un único lenguaje"
          ],
          "correcta": 1,
          "exp": "El objetivo del programa es el conocimiento transferible: ante un lenguaje nuevo, identificar dónde encaja en el mapa de conceptos y preguntar qué difiere y por qué. Esa capacidad de mapeo, no la memoria de sintaxis, es el resultado buscado."
        },
        {
          "q": "El equipo propone añadir un séptimo lenguaje al sistema para un componente pequeño. ¿Qué criterio debe pesar en la decisión más allá de la adecuación técnica?",
          "opciones": [
            "El coste operativo permanente que introduce: un toolchain más en CI, otro conjunto de dependencias que auditar, más contexto que mantener en el equipo y un lenguaje más en el que alguien deberá estar de guardia",
            "Ninguno: si el lenguaje encaja técnicamente, siempre conviene añadirlo",
            "Únicamente la popularidad del lenguaje en las encuestas del año",
            "Solo el rendimiento en microbenchmarks del componente"
          ],
          "correcta": 0,
          "exp": "El poliglotismo se justifica cuando la ventaja supera su coste continuo. Ese coste no aparece en la primera semana sino en el mantenimiento: builds, actualizaciones de seguridad, incidentes y rotación de personas. Por eso la elección se defiende por componente y no por gusto."
        },
        {
          "q": "Un error en el servicio Rust debe llegar al frontend TypeScript pasando por una API. ¿Cómo se maneja correctamente esa propagación a través de la frontera?",
          "opciones": [
            "Reenviando la excepción o el `Result` nativo tal cual, ya que todos los lenguajes los entienden",
            "Ignorando el error en la frontera y dejando que el cliente agote el tiempo de espera",
            "Traduciéndolo a la representación que define el contrato (código de estado, tipo de error y mensaje acordados), porque los mecanismos de error nativos no cruzan la frontera y cada componente los reconstruye en su propio modelo",
            "Escribiendo el error solo en el log del servidor y devolviendo siempre éxito"
          ],
          "correcta": 2,
          "exp": "Las excepciones y los tipos `Result` viven dentro de un runtime; en la frontera solo viajan datos. El contrato debe incluir el vocabulario de errores igual que el de éxitos, y cada lado lo mapea a lo idiomático suyo: excepción en Python, `Result` en Rust, rechazo de promesa en TS."
        },
        {
          "q": "El proyecto integrador empaqueta cada componente en un contenedor con su propio runtime en lugar de instalar todos los toolchains en la máquina de despliegue. ¿Qué problema resuelve?",
          "opciones": [
            "Hace que todos los componentes se ejecuten en el mismo lenguaje",
            "Elimina la necesidad de definir contratos entre servicios",
            "Reduce a cero la latencia de red entre componentes",
            "Aísla y hace reproducible el entorno de cada pieza: sus versiones y dependencias no colisionan con las de las demás, y lo probado en CI es la misma imagen que se ejecuta en producción"
          ],
          "correcta": 3,
          "exp": "El sistema políglota multiplica los entornos, y con ellos los conflictos de versiones y el clásico \"en mi máquina funciona\". El contenedor convierte el entorno en un artefacto versionado que acompaña al código, alineando desarrollo, CI y producción sin unificar los lenguajes."
        }
      ]
    }
  ]
}
