{
  "assessmentTests": {
    "python_test": {
      "name": "Prueba de Python",
      "desc": "30 preguntas de escenario sobre sintaxis, funciones, estructuras de datos y comportamiento de objetos — descubre si tu Python se ajusta a lo que una oferta de trabajo entiende por competencia en Python.",
      "recommendation": "Tu perfil de habilidades en Python",
      "results": {
        "beginner": {
          "name": "Principiante",
          "desc": "Puedes seguir un script corto y cambiar valores dentro de él, pero las preguntas que no respondiste correctamente se agrupan alrededor del comportamiento específico del lenguaje en lugar de la lógica general — un argumento predeterminado que se construye una sola vez y se reutiliza, is versus == en dos listas que se ven iguales, una variable que se vuelve local en el momento en que le asignas un valor en cualquier lugar de una función. Nada de esto se trata de ser malo en programación; estas son reglas específicas de Python que engañan a la gente que viene de otros lenguajes tan a menudo como a los principiantes. Importan en el trabajo porque cada uno es un lugar donde se esconde un error en código que se ve correcto a primera vista.",
          "recommendation": "Comienza con tres cosas, en este orden: argumentos predeterminados mutables (por qué basket=[] es peligroso), la diferencia entre is y ==, y cómo funciona el alcance de variables dentro de una función. El tutorial oficial de Python en docs.python.org cubre los tres directamente, y el término «Python gotchas» encuentra ejemplos resueltos de cada uno."
        },
        "intermediate": {
          "name": "Intermedio",
          "desc": "Manejas los scripts cotidianos cómodamente — estructuras de datos, flujo de control, funciones sencillas — y no te ralentizarías por trabajo rutinario de mantenimiento o una pequeña característica en una base de código existente. La brecha entre aquí y Avanzado es principalmente lo que sucede cuando las características de conveniencia de Python interactúan entre sí: una copia superficial que deja datos anidados compartidos cuando esperabas que fueran duplicados, un valor de retorno de un generador que silenciosamente no se convierte en parte de su salida, decoradores apilados en un orden que es fácil de invertir. Ese es el tipo de errores que un revisor senior señala en diez segundos y todos los demás pasan una hora persiguiendo.",
          "recommendation": "Enfócate en las entrañas de objetos e iteración: copia superficial versus copia profunda, qué hace realmente un generador con return, y cómo funciona el orden de apilamiento de decoradores. Luego bases de clases que la mayoría de tutoriales omiten — igualdad predeterminada sin __eq__, y por qué un atributo de clase mutable se comparte entre cada instancia."
        },
        "advanced": {
          "name": "Avanzado",
          "desc": "Este es el nivel que la mayoría de ofertas de trabajo entienden por «Python fuerte». Llegas al herramienta correcta sin buscarla, sabes por qué un trozo de estado compartido cambió en lugar de solo que lo hizo, y puedes leer la función de alguien más y predecir su salida correctamente. Lo que separa esta banda de la superior es el lado defensivo del trabajo: saber cuál de dos enfoques que se ven correctos se comportará mal una vez que un atributo de clase, un argumento de solo palabra clave, o una jerarquía de excepciones estén involucrados.",
          "recommendation": "Profundiza en las partes que protegen una base de código que otras personas también tocan: argumentos de solo palabra clave como una opción de diseño de API en lugar de una curiosidad sintáctica, la jerarquía de excepciones (para que las cláusulas except capturen la familia correcta de error, no solo una clase), y gestores de contexto como la forma estándar de garantizar la limpieza. Los artículos de análisis profundo de Real Python son un buen siguiente paso para los tres."
        },
        "expert": {
          "name": "Experto",
          "desc": "Puntuaste en la parte superior de cada sección — sintaxis central, funciones y alcance, estructuras de datos, e idiomas orientados a objetos. Prácticamente, eso significa que puedes recibir el archivo de un extraño y explicar por qué produce la salida que produce, no solo lo que la sintaxis dice que debería hacer, que es la habilidad más difícil y más valiosa. A este nivel, el lenguaje sin procesar rara vez es el factor limitante en tu trabajo; el límite suele ser el tamaño o la forma del sistema alrededor del código.",
          "recommendation": "Los retornos ahora están en herramientas y revisión: verificación de tipos estáticos con mypy o pyright, perfilado antes de optimizar en lugar de adivinar, y lectura de solicitudes de extracción de otras personas buscando exactamente esta categoría de error — los que superan cada prueba y aún hacen lo incorrecto bajo una entrada específica. Si estás siendo evaluado para un puesto, describe un error como este que encontraste en lugar de nombrar características del lenguaje — demuestra el razonamiento, no solo el vocabulario."
        }
      },
      "questions": [
        {
          "question": "Escribes def add_item(item, basket=[]): basket.append(item); return basket, luego llamas a add_item('a') y add_item('b'), ambas veces dejando basket fuera. ¿Qué devuelve la segunda llamada?",
          "options": [
            {
              "icon": "",
              "label": "['b'] — cada llamada obtiene una lista vacía nueva por defecto"
            },
            {
              "icon": "",
              "label": "TypeError — debes proporcionar un argumento basket"
            },
            {
              "icon": "",
              "label": "['a', 'b'] — la lista predeterminada se crea una vez y se reutiliza entre llamadas"
            },
            {
              "icon": "",
              "label": "['a'] luego ['b'] como dos listas independientes de un solo elemento"
            }
          ]
        },
        {
          "question": "a = [1, 2, 3] y b = [1, 2, 3]. ¿Qué devuelve a == b, y qué devuelve a is b?",
          "options": [
            {
              "icon": "",
              "label": "True para ambas — las listas idénticas siempre son el mismo objeto"
            },
            {
              "icon": "",
              "label": "True para ==, False para is — contenidos iguales pero objetos diferentes en memoria"
            },
            {
              "icon": "",
              "label": "False para ambas — literales separados nunca son iguales en Python"
            },
            {
              "icon": "",
              "label": "True para is, False para == — is verifica igualdad de valor para listas"
            }
          ]
        },
        {
          "question": "En Python 3, ¿a qué se evalúa 7 / 2, y a qué se evalúa 7 // 2?",
          "options": [
            {
              "icon": "",
              "label": "3.5, luego 3 — / siempre devuelve un float, // redondea hacia abajo el resultado"
            },
            {
              "icon": "",
              "label": "3, luego 3.5 — / trunca y // devuelve un float"
            },
            {
              "icon": "",
              "label": "3.5, luego 3.5 — ambos operadores devuelven un float en Python 3"
            },
            {
              "icon": "",
              "label": "3, luego 3 — ambos operadores redondean hacia abajo en Python 3"
            }
          ]
        },
        {
          "question": "s = 'hello', luego ejecutas s[0] = 'H'. ¿Qué sucede?",
          "options": [
            {
              "icon": "",
              "label": "s se convierte en 'Hello'"
            },
            {
              "icon": "",
              "label": "s se convierte en 'Hhello' porque el carácter se inserta, no se reemplaza"
            },
            {
              "icon": "",
              "label": "TypeError — las cadenas son inmutables y no admiten asignación de elementos"
            },
            {
              "icon": "",
              "label": "Nada — la asignación se ignora silenciosamente y s permanece 'hello'"
            }
          ]
        },
        {
          "question": "x = [10, 20, 30, 40, 50]. ¿Qué devuelve x[1:4]?",
          "options": [
            {
              "icon": "",
              "label": "[20, 30, 40, 50]"
            },
            {
              "icon": "",
              "label": "[10, 20, 30, 40]"
            },
            {
              "icon": "",
              "label": "[20, 30, 40]"
            },
            {
              "icon": "",
              "label": "[30, 40]"
            }
          ]
        },
        {
          "question": "name = 'Ana' y age = 30. ¿Qué produce f'{name} is {age}'?",
          "options": [
            {
              "icon": "",
              "label": "{name} is {age}"
            },
            {
              "icon": "",
              "label": "Ana is 30"
            },
            {
              "icon": "",
              "label": "name is age"
            },
            {
              "icon": "",
              "label": "Un TypeError, porque las f-strings requieren el método format()"
            }
          ]
        },
        {
          "question": "¿A qué se evalúa 1 < 2 < 3?",
          "options": [
            {
              "icon": "",
              "label": "True — Python encadena las comparaciones como (1 < 2) and (2 < 3)"
            },
            {
              "icon": "",
              "label": "False — las comparaciones encadenadas se evalúan de izquierda a derecha sin cortocircuito, dando 1 < 2 < 3 -> 1 < True -> False"
            },
            {
              "icon": "",
              "label": "Un SyntaxError — las comparaciones no pueden ser encadenadas"
            },
            {
              "icon": "",
              "label": "3 — Python devuelve el último valor en la cadena"
            }
          ]
        },
        {
          "question": "¿Qué devuelve bool([]), y qué devuelve bool([0])?",
          "options": [
            {
              "icon": "",
              "label": "False, luego False — cualquier lista que contenga solo valores falsos es falsa"
            },
            {
              "icon": "",
              "label": "False, luego True — una lista vacía es falsa, una lista que contiene un elemento (incluso 0) es verdadera"
            },
            {
              "icon": "",
              "label": "True, luego True — los objetos no None siempre son verdaderos"
            },
            {
              "icon": "",
              "label": "True, luego False — una lista es verdadera solo cuando está vacía"
            }
          ]
        },
        {
          "question": "Existe un count = 0 a nivel de módulo. Dentro de def increment(): count += 1; return count, llamas a increment(). ¿Qué sucede?",
          "options": [
            {
              "icon": "",
              "label": "UnboundLocalError — asignar a count en cualquier lugar de la función lo hace local, por lo que count += 1 intenta leer una variable local antes de ser asignada"
            },
            {
              "icon": "",
              "label": "Devuelve 1 e incrementa el count a nivel de módulo a 1"
            },
            {
              "icon": "",
              "label": "Devuelve 1 pero deja el count a nivel de módulo en 0"
            },
            {
              "icon": "",
              "label": "Plantea NameError porque count nunca fue declarado dentro de la función"
            }
          ]
        },
        {
          "question": "def f(*args, **kwargs): return args, kwargs. ¿Qué devuelve f(1, 2, x=3)?",
          "options": [
            {
              "icon": "",
              "label": "([1, 2], {'x': 3})"
            },
            {
              "icon": "",
              "label": "((1, 2), {'x': 3})"
            },
            {
              "icon": "",
              "label": "(1, 2, {'x': 3})"
            },
            {
              "icon": "",
              "label": "TypeError — los argumentos posicionales y de palabra clave no pueden mezclarse en una llamada"
            }
          ]
        },
        {
          "question": "Construyes funcs = [lambda: i for i in range(3)] en un bucle. ¿Qué devuelve [f() for f in funcs]?",
          "options": [
            {
              "icon": "",
              "label": "[2, 2, 2] — cada lambda busca i cuando se llama, y para entonces el bucle ha terminado con i igual a 2"
            },
            {
              "icon": "",
              "label": "[0, 1, 2] — cada lambda captura el valor de i en el momento en que fue creada"
            },
            {
              "icon": "",
              "label": "[0, 0, 0] — las lambdas siempre capturan el primer valor asignado a una variable"
            },
            {
              "icon": "",
              "label": "Un NameError, porque i ya no existe una vez que el bucle termina"
            }
          ]
        },
        {
          "question": "Una función se define con @a directamente encima de @b, que se sienta directamente encima de def f(): pass. ¿En qué orden se aplican los decoradores a f?",
          "options": [
            {
              "icon": "",
              "label": "b envuelve f primero, luego a envuelve el resultado de b — el decorador más cercano a la función se ejecuta primero"
            },
            {
              "icon": "",
              "label": "a envuelve f primero, luego b envuelve el resultado — los decoradores se aplican de arriba hacia abajo"
            },
            {
              "icon": "",
              "label": "Ambos decoradores envuelven f independientemente y solo el último definido tiene efecto"
            },
            {
              "icon": "",
              "label": "Depende del orden en que se importan los decoradores, no de su posición en el código"
            }
          ]
        },
        {
          "question": "def f(a, *, b): return a + b. ¿Cuál de estas llamadas funciona?",
          "options": [
            {
              "icon": "",
              "label": "f(1, b=2) — b debe pasarse por palabra clave porque viene después del * desnudo en la firma"
            },
            {
              "icon": "",
              "label": "f(1, 2) — los argumentos posicionales llenan los parámetros de izquierda a derecha independientemente del *"
            },
            {
              "icon": "",
              "label": "f(a=1, 2) — los argumentos de palabra clave deben venir primero"
            },
            {
              "icon": "",
              "label": "f(1) — b es opcional porque sigue al *"
            }
          ]
        },
        {
          "question": "Una función generadora yield 1, luego yield 2, luego hace return 99. ¿Qué contiene list(the_generator()), y qué sucede con 99?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 99] — return dentro de un generador se comporta como un yield más"
            },
            {
              "icon": "",
              "label": "[99] — return anula inmediatamente cualquier valor ya generado"
            },
            {
              "icon": "",
              "label": "[1, 2] — el valor de retorno de un generador se convierte en el valor adjunto al StopIteration que genera, no es generado"
            },
            {
              "icon": "",
              "label": "Un RuntimeError, porque los generadores no pueden contener una declaración return"
            }
          ]
        },
        {
          "question": "En una función outer(), se establece x = 1, luego una inner() anidada hace nonlocal x; x += 1, y outer() llama a inner() antes de devolver x. ¿Qué devuelve outer()?",
          "options": [
            {
              "icon": "",
              "label": "1 — inner() opera en su propia copia local de x, por lo que el cambio no persiste"
            },
            {
              "icon": "",
              "label": "Un UnboundLocalError, porque x no está declarado dentro de inner() antes de la declaración nonlocal"
            },
            {
              "icon": "",
              "label": "Un SyntaxError — nonlocal solo se puede usar dentro de clases"
            },
            {
              "icon": "",
              "label": "2 — nonlocal permite que inner() modifique la x de la función externa en lugar de crear una nueva variable local"
            }
          ]
        },
        {
          "question": "d = {'a': 1, 'b': 2}. ¿Qué devuelve d.get('c', 0), y agrega 'c' al diccionario?",
          "options": [
            {
              "icon": "",
              "label": "0, y no — get() devuelve el valor predeterminado sin insertar la clave"
            },
            {
              "icon": "",
              "label": "0, y sí — get() inserta la clave con el valor predeterminado"
            },
            {
              "icon": "",
              "label": "KeyError, porque 'c' no existe"
            },
            {
              "icon": "",
              "label": "None, y sí — get() siempre agrega claves faltantes con un valor None"
            }
          ]
        },
        {
          "question": "t = (1, 2, [3, 4]). ¿Puedes ejecutar t[2].append(5), y puedes ejecutar t[0] = 10?",
          "options": [
            {
              "icon": "",
              "label": "Ambas funcionan — anidar una lista dentro de una tupla hace que toda la tupla sea mutable"
            },
            {
              "icon": "",
              "label": "Ambas lanzan TypeError — nada dentro de una tupla puede ser cambiado nunca"
            },
            {
              "icon": "",
              "label": "t[2].append(5) lanza TypeError, pero t[0] = 10 funciona porque las tuplas permiten la reasignación de su primer elemento"
            },
            {
              "icon": "",
              "label": "t[2].append(5) funciona porque la lista dentro es mutable; t[0] = 10 lanza TypeError porque la tupla en sí es inmutable"
            }
          ]
        },
        {
          "question": "¿Cuál de estos devuelve un iterador que calcula valores perezosamente en lugar de construir la secuencia completa en memoria de una sola vez: [x*x for x in range(1000000)] o (x*x for x in range(1000000))?",
          "options": [
            {
              "icon": "",
              "label": "El que tiene paréntesis, (x*x for x in range(1000000)) — esa es una expresión generadora"
            },
            {
              "icon": "",
              "label": "El que tiene corchetes — las comprensiones de lista siempre son perezosas en Python 3"
            },
            {
              "icon": "",
              "label": "Ambas son perezosas — la única diferencia es el tipo devuelto al final"
            },
            {
              "icon": "",
              "label": "Ninguna — ambas construyen la secuencia completa en memoria inmediatamente"
            }
          ]
        },
        {
          "question": "Un bloque try divide por cero, la cláusula except ZeroDivisionError imprime 'a', y una cláusula finally imprime 'b'. ¿Qué se imprime, y en qué orden?",
          "options": [
            {
              "icon": "",
              "label": "b luego a — finally siempre se ejecuta antes de cualquier cláusula except"
            },
            {
              "icon": "",
              "label": "a luego b — el except que coincide se ejecuta primero, y finally siempre se ejecuta después sin importar"
            },
            {
              "icon": "",
              "label": "Solo b — la excepción en realidad no se captura, por lo que 'a' nunca se imprime"
            },
            {
              "icon": "",
              "label": "Solo a — finally se omite una vez que una excepción ya ha sido manejada"
            }
          ]
        },
        {
          "question": "import copy; a = [[1, 2], [3, 4]]; b = copy.copy(a); b[0].append(99). ¿Cambia también a[0]?",
          "options": [
            {
              "icon": "",
              "label": "No — copy.copy() siempre duplica también objetos anidados"
            },
            {
              "icon": "",
              "label": "No — agregar a b[0] crea una lista completamente nueva solo para b[0]"
            },
            {
              "icon": "",
              "label": "Sí, pero solo porque copy.copy() fue llamado sin el argumento deepcopy"
            },
            {
              "icon": "",
              "label": "Sí — copy.copy() hace una copia superficial, por lo que las listas anidadas se comparten entre a y b"
            }
          ]
        },
        {
          "question": "d = {'x': 1, 'y': 2}. ¿Qué verifica 'x' in d — las claves, los valores, o ambas?",
          "options": [
            {
              "icon": "",
              "label": "Los valores — `in` verifica si 1 o 2 es igual a 'x'"
            },
            {
              "icon": "",
              "label": "Se verifican tanto las claves como los valores"
            },
            {
              "icon": "",
              "label": "Ninguno — `in` en un dict siempre lanza TypeError"
            },
            {
              "icon": "",
              "label": "Las claves — `in` en un diccionario prueba la pertenencia entre sus claves, no sus valores"
            }
          ]
        },
        {
          "question": "names = ['Al', 'Bo'] y scores = [10, 20]. ¿Qué devuelve list(zip(names, scores))?",
          "options": [
            {
              "icon": "",
              "label": "[('Al', 'Bo'), (10, 20)]"
            },
            {
              "icon": "",
              "label": "['Al', 10, 'Bo', 20]"
            },
            {
              "icon": "",
              "label": "[('Al', 10), ('Bo', 20)]"
            },
            {
              "icon": "",
              "label": "Un ValueError, porque zip requiere tres o más argumentos"
            }
          ]
        },
        {
          "question": "class Dog: tricks = [] contiene una lista a nivel de clase, y add_trick agrega a self.tricks. Se crean d1 = Dog() y d2 = Dog(), luego se ejecuta d1.add_trick('sit'). ¿Qué contiene d2.tricks?",
          "options": [
            {
              "icon": "",
              "label": "[] — cada instancia obtiene su propia copia independiente de tricks"
            },
            {
              "icon": "",
              "label": "['sit'] — tricks es una lista a nivel de clase compartida por cada instancia, por lo que el cambio de d1 es visible en d2 también"
            },
            {
              "icon": "",
              "label": "Un TypeError — las instancias no pueden agregar a un atributo de clase"
            },
            {
              "icon": "",
              "label": "['sit'] solo después de que d2 = Dog() se vuelva a ejecutar"
            }
          ]
        },
        {
          "question": "class A tiene def greet(self): return 'A'. class B(A) tiene def greet(self): return 'B' + super().greet(). ¿Qué devuelve B().greet()?",
          "options": [
            {
              "icon": "",
              "label": "'AB' — super() siempre se ejecuta antes del propio código de la subclase"
            },
            {
              "icon": "",
              "label": "'B' — super().greet() se ignora porque B ya anula greet"
            },
            {
              "icon": "",
              "label": "'BA' — super().greet() llama al método de A y su resultado se agrega a 'B'"
            },
            {
              "icon": "",
              "label": "Un AttributeError, porque A no tiene método greet visible para B"
            }
          ]
        },
        {
          "question": "class Point almacena self.x en __init__ y no define otros métodos. Se crean p1 = Point(1) y p2 = Point(1). ¿Qué devuelve p1 == p2 por defecto?",
          "options": [
            {
              "icon": "",
              "label": "True — Python compara todos los atributos coincidentes por defecto"
            },
            {
              "icon": "",
              "label": "False — sin un __eq__ personalizado, == regresa a comparación de identidad, y p1 y p2 son objetos diferentes"
            },
            {
              "icon": "",
              "label": "True, pero solo porque ambos objetos fueron creados con el mismo argumento"
            },
            {
              "icon": "",
              "label": "Un TypeError, porque Point no define __eq__"
            }
          ]
        },
        {
          "question": "¿Cuál cláusula except a continuación también capturará un ZeroDivisionError: except ArithmeticError, except ValueError, o except KeyError?",
          "options": [
            {
              "icon": "",
              "label": "except ValueError — los errores de división se tratan como valores inválidos"
            },
            {
              "icon": "",
              "label": "except KeyError — cualquier error en tiempo de ejecución no vinculado a un tipo específico regresa a KeyError"
            },
            {
              "icon": "",
              "label": "Ninguno de ellos — solo except ZeroDivisionError en sí mismo puede capturarlo"
            },
            {
              "icon": "",
              "label": "except ArithmeticError — ZeroDivisionError es una subclase de ArithmeticError"
            }
          ]
        },
        {
          "question": "Abres un archivo con open('data.txt') as f: y se genera una excepción mientras lees dentro del bloque. ¿Se cierra el archivo de todos modos?",
          "options": [
            {
              "icon": "",
              "label": "No — el archivo permanece abierto porque el bloque no terminó normalmente"
            },
            {
              "icon": "",
              "label": "Solo si también agregamos una cláusula finally alrededor de la declaración with"
            },
            {
              "icon": "",
              "label": "Sí — la declaración with llama al método __exit__ del archivo, que lo cierra, incluso si ocurre una excepción dentro del bloque"
            },
            {
              "icon": "",
              "label": "Depende del tipo de excepción — solo las excepciones capturadas en otra parte desencadenan la limpieza"
            }
          ]
        },
        {
          "question": "x = [3, 1, 2] y y = x.sort(). ¿Qué contiene y?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 3] — sort() devuelve la lista recién ordenada"
            },
            {
              "icon": "",
              "label": "None — list.sort() ordena en su lugar y devuelve None; x en sí se convierte en [1, 2, 3]"
            },
            {
              "icon": "",
              "label": "[3, 1, 2] — sort() devuelve la lista original sin ordenar y solo cambia el orden interno de x"
            },
            {
              "icon": "",
              "label": "Un TypeError, porque sort() no puede ser asignado a una variable"
            }
          ]
        },
        {
          "question": "¿Qué sucede cuando ejecutas 'Score: ' + 42?",
          "options": [
            {
              "icon": "",
              "label": "'Score: 42' — Python convierte el número automáticamente dentro de una expresión +"
            },
            {
              "icon": "",
              "label": "'Score: ' — el número se descarta silenciosamente"
            },
            {
              "icon": "",
              "label": "42 — la cadena es coercionada a un número y agregada"
            },
            {
              "icon": "",
              "label": "TypeError — no puedes concatenar una str y un int directamente, 42 debe ser convertido con str() primero"
            }
          ]
        },
        {
          "question": "Necesitas verificar si una lista llamada results está vacía. ¿Cuál es la forma idiomática de Python: if results == []: , if len(results) == 0: , o if not results: ?",
          "options": [
            {
              "icon": "",
              "label": "if results == []: — esta es la única forma confiable de detectar una lista vacía"
            },
            {
              "icon": "",
              "label": "if len(results) == 0: — esto es requerido porque las listas vacías son verdaderas en Python"
            },
            {
              "icon": "",
              "label": "Los tres se usan nunca en la práctica; el enfoque estándar es if results is None:"
            },
            {
              "icon": "",
              "label": "if not results: — el idioma Python se basa en la veracidad, y una lista vacía es falsa"
            }
          ]
        }
      ],
      "optionOrderVersion": "92945565db6c55da"
    }
  }
}
