Bloque III · Tema 2 · Cuerpo de Técnicos Auxiliares de Informática
Así lo numera la convocatoria: Bloque III. Desarrollo de sistemas. En nuestro temario es el tema 16 de 33.
Lenguajes de programación. Representación de tipos de datos. Operadores. Instrucciones condicionales. Bucles y recursividad. Procedimientos, funciones y parámetros. Vectores y registros. Estructura de un programa.
El tema que cambia el registro del examen: aquí no se aprueba recordando, se aprueba TRAZANDO. El fragmento más repetido del corpus —tres veces en 2024— es un bucle de cuatro instrucciones cuya respuesta es «es un bucle infinito», y no hace falta teoría para acertarlo: hace falta seguirlo con el dedo. Con la representación real de los datos (complemento a dos, IEEE 754 y sus 32 y 64 bits, Unicode), la precedencia de operadores y el cortocircuito, las estructuras de control con las cuatro parejas que el tribunal intercambia sin avisar (función/procedimiento, valor/referencia, while/do-while, igualdad estricta y no estricta), recursividad trazada paso a paso, vectores, matrices y registros, errores y excepciones, y las peculiaridades de los seis lenguajes que este tribunal ha llegado a preguntar: Java, Python, JavaScript, C#, C++ y F#.
Tema desarrollado para el ejercicio único del Cuerpo de Técnicos Auxiliares de Informática de la Administración del Estado. Es el tema 2 del bloque III «Desarrollo de sistemas», que en nuestra numeración interna es el tema 16 de 33. Todo el código de este apunte está escrito para leerse y trazarse a mano, que es exactamente lo que pide el examen.
Qué esperar de este tema en el examen. Cambia el registro respecto del tema anterior. Si el de modelado de datos se contestaba reconociendo la frase literal de MÉTRICA, este se contesta leyendo código y siguiéndolo con el dedo. Hemos contado 77 preguntas del corpus oficial que se responden con esta materia, y su firma es inconfundible:
- Te dan un fragmento y preguntan qué hace. El caso más repetido del corpus —tres veces en 2024, modelos A, B y promoción interna— es un bucle de cuatro instrucciones cuya respuesta es «este programa es un bucle infinito». No hay que saber teoría: hay que trazar.
- Cuatro declaraciones casi idénticas y solo una compila. Un array en C#, un import en Python, un atributo required en HTML, un tipo anulable en C#. La diferencia entre la buena y las malas es un corchete, un signo de interrogación o el orden de dos palabras.
- Afirmaciones sobre un lenguaje concreto, con una falsa. Java, JavaScript, Python, C++, C# y hasta F#. Aquí el tribunal es tramposo de verdad: en 2026 la opción falsa era «
123 == '123'devolverá falso», que en JavaScript es verdadero por conversión implícita de tipos. - Y pregunta la teoría clásica en su versión precisa. Compilador frente a intérprete frente a transpilador; funciones frente a procedimientos; paso por valor frente a paso por referencia; do…while frente a while.
Aviso de calibración, porque importa: las versiones concretas que aparecen en los enunciados son las que el tribunal eligió en su día, no las vigentes hoy. El examen de 2026 habla de «Java JDK 17», «Python 3.11» y «ECMAScript 2022»; a día de hoy la LTS de Java es la 25, Python va por la 3.14 y la edición vigente de ECMAScript es la de 2026. Este apunte cita las versiones con su fecha y no llama «actual» a ninguna, porque eso caduca solo.
Índice
- Qué es un lenguaje de programación
- Del código fuente al ejecutable
- Representación de los tipos de datos
- Operadores
- Instrucciones condicionales
- Bucles
- Recursividad
- Procedimientos, funciones y parámetros
- Vectores y registros
- Estructura de un programa
- Errores y excepciones
- Los seis lenguajes que este tribunal pregunta
- Lo que ya ha preguntado el tribunal
1. Qué es un lenguaje de programación
Un lenguaje de programación es un lenguaje formal con el que se escriben instrucciones que una máquina puede ejecutar. Como todo lenguaje formal, se describe en tres planos, y conviene saber nombrarlos porque las preguntas de «¿por qué falla este código?» se contestan diciendo en cuál de los tres está el problema:
- Léxico: qué piezas existen. Palabras reservadas, identificadores, literales, operadores y separadores. A cada pieza se la llama token. Un error léxico es escribir un carácter que el lenguaje no admite.
- Sintaxis: cómo se combinan esas piezas. Se describe con una gramática formal, normalmente en notación BNF —de Backus y Naur— o EBNF, que es la versión extendida y está normalizada como ISO/IEC 14977; las gramáticas formales son la herramienta con la que se fija sin ambigüedad qué frases pertenecen al lenguaje. Un error sintáctico es olvidar un paréntesis.
- Semántica: qué significa cada combinación. Un error semántico es sumar un número a una cadena cuando el lenguaje no lo permite: está bien escrito, pero no quiere decir nada válido.
1.1. Niveles y generaciones
Se habla de nivel «bajo» o «alto» según lo cerca que esté el lenguaje de la máquina o de la persona. Y en paralelo se usa la clasificación por generaciones, que es la que el tribunal ha preguntado:
| Generación | Qué es | Ejemplos |
|---|---|---|
| 1GL | Código máquina: ceros y unos que el procesador ejecuta directamente. | — |
| 2GL | Ensamblador: una mnemotecnia por instrucción máquina. Depende del procesador. | Ensamblador x86, ARM |
| 3GL | Alto nivel de propósito general: se escribe con estructuras de control y variables, y hace falta traducirlo. | C, C++, Java, Python, C# |
| 4GL | Muy próximos al dominio del problema; se dice qué se quiere, no cómo. | SQL, generadores de informes |
| 5GL | Basados en lógica y restricciones: se describe el problema y el motor busca la solución. | Prolog |
🚨 Cayó dos veces como distractor: «C++ es un lenguaje de segunda generación» es FALSO. C++ es de tercera; la segunda generación es el ensamblador. La regla corta: si tiene bucles y variables con nombre, es 3GL como mínimo.
1.2. Paradigmas
Un paradigma es la forma de organizar el programa. Casi todos los lenguajes actuales son multiparadigma, así que la clasificación describe estilos, no compartimentos:
- Imperativo: se describe una secuencia de instrucciones que cambian el estado. Dentro está el estructurado (solo secuencia, selección e iteración) y el procedimental (con subprogramas).
- Orientado a objetos: el programa es un conjunto de objetos que se envían mensajes. Es el tema 4 de este bloque.
- Funcional: se compone a base de funciones sin efectos laterales, y los datos son inmutables. Lisp, Haskell, F#, y las partes funcionales de Java, Python o JavaScript.
- Lógico: se declaran hechos y reglas y el motor deduce. Prolog.
- Declarativo: se describe el resultado, no el procedimiento. SQL y HTML son declarativos.
2. Del código fuente al ejecutable
2.1. Compilador, intérprete, transpilador y compilador cruzado
Cuatro palabras que se parecen y que el tribunal ha puesto juntas, con una definición falsa entre ellas. Van con precisión:
| Herramienta | Qué hace exactamente |
|---|---|
| Compilador | Transforma el código fuente en su equivalente en otro lenguaje de más bajo nivel, normalmente código objeto o máquina. Traduce todo antes de ejecutar. |
| Intérprete | Ejecuta directamente las instrucciones del programa, una a una, sin producir un ejecutable previo. |
| Transpilador | Es un compilador de fuente a fuente: traduce entre lenguajes del mismo nivel de abstracción. TypeScript a JavaScript, o JavaScript moderno a JavaScript antiguo con Babel. |
| Compilador cruzado | Compilador capaz de generar ejecutable para una plataforma distinta de aquella en la que se ejecuta. Es lo normal al programar para un microcontrolador o para un móvil. |
🚨 La pregunta de 2023 pedía la afirmación FALSA, y era: «un transpilador es un programa que hace de intérprete y de compilador al mismo tiempo». No: el transpilador no interpreta nada, solo traduce, y traduce en horizontal, de un lenguaje a otro del mismo nivel. Las otras tres opciones eran las tres definiciones correctas de la tabla.
Las diferencias prácticas entre compilar e interpretar, que también se preguntan:
| Compilado | Interpretado | |
|---|---|---|
| Errores | Se detectan antes de ejecutar, todos de golpe | Aparecen al llegar a la línea |
| Velocidad de ejecución | Mayor | Menor |
| Portabilidad del resultado | Ligada a la plataforma | El fuente corre donde haya intérprete |
| Ciclo de desarrollo | Más lento (hay que recompilar) | Más rápido |
2.2. Las fases de un compilador
Seis fases, en dos mitades. La anterior (front-end) analiza y depende del lenguaje; la posterior (back-end) sintetiza y depende de la máquina. Por eso un mismo front-end sirve para varias arquitecturas.
- Análisis léxico (o rastreo): parte el fuente en tokens y descarta espacios y comentarios.
- Análisis sintáctico (o parsing): comprueba que los tokens forman frases válidas y construye el árbol sintáctico.
- Análisis semántico: comprueba lo que la gramática no puede, sobre todo la coherencia de tipos y que las variables estén declaradas. Aquí se llena la tabla de símbolos.
- Generación de código intermedio: una representación independiente de la máquina.
- Optimización: mejora el código intermedio sin cambiar lo que hace.
- Generación de código objeto: produce el código de la máquina destino.
Después vienen dos pasos que no son del compilador y que a menudo se confunden con él: el enlazador (linker), que une los módulos objeto y las bibliotecas en un ejecutable, y el cargador, que lo lleva a memoria. El enlazado puede ser estático (la biblioteca queda dentro del ejecutable: más grande, pero autónomo) o dinámico (se resuelve al ejecutar contra un fichero .dll o .so: más pequeño, pero dependiente).
2.3. Bytecode, máquina virtual y JIT
Java y .NET no encajan del todo ni en compilado ni en interpretado, y por eso se les llama híbridos. El esquema, que hay que tener claro:
- El compilador (
javacen Java) traduce el fuente a bytecode, un código intermedio que no es código máquina de ningún procesador real: es el juego de instrucciones de una máquina virtual. En .NET el equivalente se llama CIL o MSIL. - La máquina virtual (la JVM, o el CLR en .NET) ejecuta ese bytecode. Ahí está la portabilidad: el mismo .class corre en cualquier sistema que tenga JVM.
- Y para no perder rendimiento, el JIT (Just-In-Time compiler) compila el código intermedio a código máquina en tiempo de ejecución, guardando el resultado para las siguientes llamadas.
🚨 Cayó en los dos modelos de 2026: «¿cuál es el componente encargado de compilar el código intermedio a código máquina en tiempo de ejecución?». Respuesta: el JIT. Los distractores eran el recolector de basura o garbage collector (que libera la memoria de los objetos sin referencias), el bytecode (que es el código, no un componente que compile) y el SDK (el kit de desarrollo). Fíjate en que el bytecode aparece como distractor de sí mismo: no es quien compila, es lo que se compila.
3. Representación de los tipos de datos
Un tipo de dato define tres cosas a la vez: el conjunto de valores posibles, las operaciones admitidas y la representación interna en memoria.
3.1. Tipos primitivos
| Tipo | Qué guarda | Detalle que se pregunta |
|---|---|---|
| Entero | Números sin parte decimal | Con signo (complemento a dos) o sin signo. En Java, int ocupa 32 bits y long 64. Si se supera el rango hay desbordamiento y el valor «da la vuelta». |
| Real o coma flotante | Números con parte decimal | Norma IEEE 754. Simple precisión (binary32) = 32 bits; doble precisión (binary64) = 64 bits. |
| Carácter | Un símbolo | Un código de una tabla: ASCII (7 bits), Latin-1 (8) o Unicode. En Java, char son 16 bits. |
| Booleano | Cierto o falso | Ocupa conceptualmente un bit, pero en la práctica un byte o una palabra. |
🚨 La pregunta de 2024: «según el estándar IEEE 754-2008, un número en coma flotante de doble precisión es…» → una aproximación de 64 bits de un número real. Dos cosas de esa frase merecen atención. La primera, que son 64 bits y no 32 —32 es la simple precisión—. La segunda, la palabra aproximación: la coma flotante no representa exactamente los reales, y por eso 0.1 + 0.2 no da 0.3 en prácticamente ningún lenguaje. Para dinero se usan tipos decimales exactos, no coma flotante.
⚠ Precisión de fechas: el examen citó la edición de 2008 de la norma, que era la vigente cuando se redactó. La norma se revisó en 2019 y esa revisión es la que está en vigor; los tamaños de los formatos no cambiaron.
3.1 bis. Cómo se guardan de verdad: complemento a dos y coma flotante
Saber cuántos bits ocupa un tipo se contesta de memoria; saber cómo los usa explica todos los comportamientos raros que el tribunal pregunta.
Enteros con signo: complemento a dos. Es la representación universal, y funciona así: el bit más significativo vale en negativo. Con 8 bits, el rango va de −128 a +127 —observa la asimetría: hay un negativo más que positivos, porque el cero ocupa un hueco del lado positivo—. Su ventaja es que la resta se hace con el mismo circuito que la suma. Y su consecuencia visible es el desbordamiento: si a un entero de 8 bits que vale 127 le sumas 1, no da 128, da −128. El número «da la vuelta». Con 32 bits, el techo está en 2.147.483.647, un número que conviene reconocer de vista porque aparece constantemente en errores reales.
Coma flotante: signo, exponente y mantisa. Un número IEEE 754 se guarda en tres campos, como la notación científica en binario:
| Formato | Total | Signo | Exponente | Mantisa | Dígitos decimales fiables |
|---|---|---|---|---|---|
| Simple (binary32) | 32 bits | 1 | 8 | 23 | ≈ 7 |
| Doble (binary64) | 64 bits | 1 | 11 | 52 | ≈ 15-16 |
De ahí sale el comportamiento que sorprende a todo el mundo: 0,1 no tiene representación exacta en binario, igual que 1/3 no la tiene en decimal. Por eso 0.1 + 0.2 no da exactamente 0.3 en prácticamente ningún lenguaje, y por eso nunca se comparan dos reales con el operador de igualdad: se comprueba que su diferencia sea menor que una tolerancia. Y por eso los importes monetarios no se guardan en coma flotante: se usan tipos decimales exactos, o enteros de céntimos.
La norma define además tres valores especiales que no son números normales y que aparecen en los exámenes de JavaScript: infinito positivo y negativo (resultado de dividir por cero en coma flotante) y NaN, Not a Number, que es lo que devuelve una operación sin sentido matemático. NaN tiene una propiedad que conviene saber: no es igual a sí mismo, así que la única forma de detectarlo es una función específica del tipo isNaN.
Caracteres. La codificación de caracteres asigna a cada símbolo un número que indexa una tabla. ASCII usa 7 bits y cubre 128 símbolos —sin eñes ni tildes—; las páginas de código de 8 bits, como Latin-1, añaden 128 más; y Unicode asigna un punto de código a cada símbolo de cualquier escritura, codificado normalmente en UTF-8, que es de longitud variable: 1 byte para el ASCII y hasta 4 para el resto. La consecuencia práctica, que se pregunta: en UTF-8 el número de bytes no coincide con el número de caracteres en cuanto aparece una eñe.
3.2. Tipos estructurados y definidos por el usuario
- Array o vector: colección homogénea —todos los elementos del mismo tipo— y de tamaño fijo, con acceso por índice.
- Registro (struct): colección heterogénea de campos con nombre.
- Cadena: secuencia de caracteres. En muchos lenguajes es un tipo aparte y, ojo, inmutable.
- Enumerado: conjunto cerrado de valores con nombre (los días de la semana).
- Puntero o referencia: guarda la dirección de otro dato.
3.3. Tipado
Cuatro ejes que se combinan y que hay que saber separar:
| Eje | Opciones | Qué significa |
|---|---|---|
| Momento | Estático / Dinámico | El tipo se comprueba al compilar (Java, C#) o al ejecutar (Python, JavaScript). |
| Rigor | Fuerte / Débil | Si el lenguaje impide mezclar tipos sin conversión explícita (Python es fuerte y dinámico) o los convierte por su cuenta (JavaScript es débil). |
| Declaración | Explícito / Inferido | Se escribe el tipo o lo deduce el compilador por inferencia de tipos (var en C#, auto en C++, let en F#). |
| Nulabilidad | Anulable / No anulable | Si el tipo admite el valor nulo. Los tipos anulables se marcan en C# con un signo de interrogación tras el tipo. |
⚠ Fuerte no es lo mismo que estático, y es la confusión más común. Python es dinámico (no declaras tipos) pero fuerte (no te deja sumar un número y una cadena). JavaScript es dinámico y débil: sí te deja, y ahí está la trampa de la que hablamos enseguida.
🚨 Cayó en 2024, dos modelos: en C#, la forma de indicar que un tipo admite nulo es int? Telefono. Ni int|null, ni int:null, ni int ?? Telefono —el doble interrogante es el operador de fusión de nulos, que devuelve el operando de la derecha si el de la izquierda es nulo; es otra cosa—.
3.4. Conversión de tipos
- Implícita o promoción: la hace el lenguaje solo, y solo cuando no se pierde información (de
intalong, defloatadouble). - Explícita o casting: la pide el programador, y puede perder información (de
doubleaintse trunca la parte decimal).
3.5. Fechas y horas
Es el tipo que más problemas da y el que el tribunal ha preguntado en tres convocatorias —en el ingreso libre de 2024 en sus dos modelos y en la promoción interna—, siempre desde el mismo supuesto: controlar el plazo de presentación de solicitudes. La pregunta era qué clase nativa de Java usar para trabajar con fecha y hora simultáneamente, y la respuesta, LocalDateTime.
Se acierta sin dudar si se entiende cómo está construida la familia: cada nombre dice exactamente lo que lleva dentro.
| Tipo en Java | Qué guarda | Equivalente en .NET |
|---|---|---|
| LocalDate | Solo la fecha: año, mes y día. Sirve para una fecha de nacimiento. | DateOnly |
| LocalTime | Solo la hora: hora, minuto, segundo. Sirve para la hora de apertura de una oficina. | TimeOnly |
| LocalDateTime | Fecha y hora juntas, sin zona horaria. Es la respuesta oficial y la que sirve para un plazo de presentación. | DateTime |
| OffsetDateTime y OffsetTime | Lo mismo, más el desfase respecto del meridiano de referencia (por ejemplo, dos horas). ⚠️ OffsetTime no lleva fecha: ese era el distractor de la pregunta. | DateTimeOffset |
| ZonedDateTime | Fecha, hora y zona horaria con nombre, con lo que sabe de cambios de horario de verano. | DateTimeOffset con TimeZoneInfo |
| Instant | Un punto exacto en la línea del tiempo, contado desde el origen de referencia. Es lo que se guarda en una traza. | DateTimeOffset en tiempo universal |
| Duration y Period | Cantidades de tiempo, no momentos: Duration mide en horas, minutos y segundos; Period, en años, meses y días. | TimeSpan |
El error que este apartado evita cuesta caro en un supuesto de plazos: «local» no significa «de mi ordenador», significa «sin zona horaria». Un LocalDateTime que dice «31 de enero a las 23:59» no sabe de qué sitio del mundo habla, así que no identifica un instante único. Para un plazo administrativo eso basta —el plazo se agota a las 23:59 hora peninsular por definición—, pero para comparar sucesos entre sistemas hace falta un tipo con zona o un instante.
Y dos rasgos de la familia que se preguntan por separado: sus objetos son inmutables, de modo que sumar un día devuelve otro objeto en lugar de modificar el que había; y sustituyó a unas clases anteriores que eran mutables, no seguras entre hilos y contaban los meses empezando por cero, que era una fuente inagotable de errores.
4. Operadores
| Familia | Operadores | Lo que se pregunta |
|---|---|---|
| Aritméticos | suma, resta, producto, división, módulo | El módulo devuelve el resto de la división entera: 7 % 3 es 1. Es el operador con el que se comprueba si un número es par. |
| Relacionales | igual, distinto, mayor, menor, mayor o igual, menor o igual | Devuelven booleano. En muchos lenguajes hay dos igualdades, y esa es la trampa. |
| Lógicos | y, o, no | Evaluación en cortocircuito: si el primer operando ya decide el resultado, el segundo no se evalúa. Es lo que permite escribir «si el objeto no es nulo Y su campo vale X» sin reventar. |
| Asignación | simple y compuesta | j += i equivale a j = j + i. |
| Incremento y decremento | prefijo y sufijo | En forma prefija el valor se modifica antes de usarse; en sufija, después. |
| Bit a bit | y, o, o exclusiva, negación, desplazamientos | Operan sobre los bits. Desplazar a la izquierda multiplica por dos; a la derecha, divide. |
| Ternario o condicional | condición, valor si cierto, valor si falso | Es una expresión, no una sentencia: devuelve un valor y puede asignarse. |
4.1. Precedencia y asociatividad
Cuando en una expresión hay varios operadores, el orden lo deciden dos reglas. La precedencia dice qué operador se aplica antes; la asociatividad resuelve el empate entre operadores de la misma precedencia, y casi siempre es de izquierda a derecha —salvo la asignación y los operadores unarios, que van de derecha a izquierda—.
El orden, de más fuerte a más débil, es prácticamente el mismo en toda la familia C:
- Paréntesis.
- Unarios: negación lógica, cambio de signo, incremento y decremento.
- Producto, división y módulo.
- Suma y resta.
- Desplazamientos de bits.
- Relacionales de orden (menor, mayor).
- Igualdad y desigualdad.
- Operadores de bits: y, o exclusiva, o.
- Y lógico, y después O lógico.
- Condicional ternario.
- Asignación, simple o compuesta.
Dos consecuencias que se preguntan. La primera: 2 + 3 * 4 vale 14, no 20. La segunda, más útil: el Y lógico tiene más precedencia que el O lógico, así que «A o B y C» se lee «A o (B y C)». Cuando la expresión no es evidente, la respuesta profesional es poner paréntesis: no cuestan nada y eliminan la ambigüedad para quien lea el código después.
Un detalle sobre el módulo: devuelve el resto, así que x % 2 == 0 es la forma canónica de preguntar si un número es par, y x % n recorre siempre los valores 0…n−1, que es lo que se usa para dar la vuelta a un índice circular.
4.2. El cortocircuito, que no es un detalle de rendimiento
En la evaluación en cortocircuito, si el primer operando ya determina el resultado, el segundo no llega a evaluarse: en un Y lógico, si el primero es falso el resultado es falso; en un O lógico, si el primero es cierto el resultado es cierto.
Se suele explicar como una optimización, y no es eso lo importante: es lo que hace correcto un patrón muy común. Escribir «si el objeto no es nulo Y su campo vale X» solo funciona porque, cuando el objeto es nulo, la segunda parte no se evalúa; sin cortocircuito el programa reventaría. Lo mismo con «si el índice está dentro del rango Y el elemento cumple la condición». Por eso importa el orden de los operandos: la comprobación defensiva va siempre primero.
4.3. La igualdad de JavaScript, que es donde cae la gente
JavaScript tiene dos operadores de igualdad y la diferencia es exactamente el tipo:
==es la igualdad no estricta: convierte los tipos antes de comparar. Por eso123 == '123'devuelve verdadero: la cadena se convierte a número.===es la igualdad estricta: compara valor y tipo, sin convertir nada.123 === '123'devuelve falso.
🚨 La pregunta de 2026 pedía la afirmación INCORRECTA sobre JavaScript, y era: «123 == '123' devolverá falso». Es falsa porque con doble igual devuelve verdadero. Las otras tres eran ciertas: entre los tipos de JavaScript están String, Boolean, Undefined y Number; una variable declarada sin valor vale undefined; y el alcance de una variable declarada con let es de bloque —frente a var, que es de función—.
Del mismo examen, otra que conviene llevar aprendida: JavaScript NO forma parte de la plataforma Java. El parecido del nombre es un asunto comercial de 1995 y nada más; son lenguajes distintos, con especificaciones distintas y organismos distintos. Y isNaN("hola") devuelve verdadero, porque la cadena no se puede convertir a número.
5. Instrucciones condicionales
- if: ejecuta un bloque si la condición es cierta.
- if / else: dos caminos excluyentes.
- if anidados o else if encadenados: selección múltiple escrita a mano.
- switch / case: selección múltiple sobre el valor de una expresión. Dos detalles que se preguntan: normalmente compara por igualdad exacta con constantes, no con rangos; y en los lenguajes de la familia C, si no se pone
break, la ejecución cae al siguiente caso. La agrupación de varioscaseseguidos aprovecha esa caída de forma deliberada. La ramadefaultrecoge lo que no encaja.
5.1. El switch y su caída en cascada
El switch merece un párrafo propio porque su comportamiento por defecto es contraintuitivo. En C, C++, Java, C# y JavaScript, cuando la ejecución entra por un case sigue hacia abajo atravesando los siguientes hasta encontrar un break o el final del bloque. Es lo que se llama fall-through, y es la causa clásica de que un menú ejecute tres opciones a la vez.
No siempre es un error: agrupar varios case seguidos sin código entre ellos es la forma idiomática de decir «para estos tres valores, haz lo mismo». Pero cuando no se pretende, hay que cerrar cada rama. C# resolvió el problema por la vía dura: obliga a terminar cada rama y no compila si te olvidas.
Otras dos precisiones: la etiqueta de un case tiene que ser una constante —no una variable ni un rango—, y la rama default recoge lo que no encaja en ninguna, pero no está obligada a ir la última, aunque escribirla en otro sitio sea una pésima idea para quien lea el código.
⚠ En Python no existe el switch clásico. Lo tradicional ha sido encadenar elif o usar un diccionario; desde la versión 3.10 hay una sentencia match que se le parece, pero que en realidad es más potente porque hace concordancia de patrones, no comparación de valores.
5.2. Delimitadores de bloque, lenguaje por lenguaje
Parece una tontería y es una pregunta de examen:
| Lenguaje | Delimita el bloque con |
|---|---|
| C, C++, Java, C#, JavaScript | Llaves |
| Pascal | begin y end |
| Visual Basic | If … End If |
| Python | La sangría: dos puntos al final de la cabecera y todas las líneas del bloque con el mismo número de espacios |
🚨 La pregunta de 2019: «en una sentencia if en Python, ¿cómo se identifica el bloque de código a ejecutar?». Respuesta: cada línea del bloque va precedida por el mismo número de espacios en blanco, con la misma sangría. Ni llaves, ni corchetes, ni paréntesis. En Python la indentación no es estilo: es sintaxis, y mezclar tabuladores con espacios provoca un error real.
6. Bucles
| Bucle | Cuándo comprueba la condición | Ejecuciones mínimas |
|---|---|---|
| while | Antes de cada pasada | Cero: si la condición es falsa de entrada, no entra nunca |
| do … while (do-while) | Después de cada pasada | Una: el cuerpo se ejecuta antes de mirar nada |
| for (contador) | Antes, con inicialización, condición e incremento juntos | Cero |
| for each (for-each) | Recorre una colección elemento a elemento | Cero (colección vacía) |
🚨 La pregunta de 2019 sobre do…while en C++ tenía cuatro opciones muy parecidas y la buena era: «se ejecuta una vez antes de evaluar la condición del bucle». Las falsas decían «solo se ejecuta si la condición es verdadera» (eso es el while), «evalúa la condición y después se ejecuta al menos una vez» (invierte el orden) y «se ejecuta una sola vez siempre que la condición sea verdadera» (le quita la repetición). La frase que hay que retener: el do…while ejecuta primero y pregunta después.
Dentro del cuerpo, dos instrucciones de salto: break abandona el bucle entero y continue abandona solo la pasada actual y sigue con la siguiente.
Y cuatro papeles clásicos de variable, que sirven para leer cualquier bucle de examen: el contador (aumenta de uno en uno), el acumulador (va sumando valores: los acumuladores de suma arrancan en 0 y los de producto en 1), el centinela (valor especial que marca el final de los datos) y el interruptor o bandera (booleano que recuerda si algo ha pasado).
6.1. El bucle infinito, y cómo se caza a mano
Es la pregunta más repetida del tema: cayó en 2024 en el modelo A, en el B y en el de promoción interna, siempre con el mismo código:
integer x = 0; while x < 100 do ( x = x + 1; print x; x = x - 1; )
Las opciones proponían que imprime los enteros del 1 al 100, los pares, los impares… y la buena era «este programa es un bucle infinito». El razonamiento, paso a paso, que es el que hay que saber hacer con cualquier fragmento:
- Entra con
x = 0. Como 0 es menor que 100, entra al cuerpo. x = x + 1dejaxen 1. Imprime 1.x = x - 1devuelvexa 0.- Vuelve a evaluar la condición: 0 sigue siendo menor que 100. Y así para siempre, imprimiendo un 1 sin parar.
La clave está en la última instrucción del cuerpo, que deshace el incremento. La regla general: un bucle termina si su variable de control avanza hacia la condición de salida en TODAS las pasadas. Si algo la revierte, la resetea o la deja igual, es infinito. Al leer un bucle en el examen, mira primero la variable de la condición y comprueba qué le pasa al final del cuerpo, no al principio.
6.2. Trazar un bucle con acumulador
El otro fragmento que cayó, esta vez en JavaScript (2019):
function prueba() { var i = 1; var j = 0; while (i < 7) { j += i; i += 2; } return j; }
Se traza con una tabla de dos columnas, que es como hay que hacerlo siempre:
| Pasada | i al entrar | j tras j += i | i tras i += 2 |
|---|---|---|---|
| 1.ª | 1 | 1 | 3 |
| 2.ª | 3 | 4 | 5 |
| 3.ª | 5 | 9 | 7 |
| — | 7 | 7 no es menor que 7: sale | |
Devuelve 9, que es la suma de los impares 1, 3 y 5. El error típico es incluir el 7: la condición es estrictamente menor, así que 7 no entra.
6.3. Los cinco esquemas de bucle que hay que reconocer de un vistazo
Casi todo bucle de examen es una variación de estos cinco. Reconocer cuál es reduce la traza a comprobar dos o tres pasadas:
| Esquema | Cómo se reconoce | Qué hay que vigilar |
|---|---|---|
| Contador | Una variable que crece de uno en uno hasta un tope. | Si el límite es «menor que» o «menor o igual»: decide si el último valor entra. |
| Acumulador | Una variable que se inicializa a 0 (suma) o a 1 (producto) y se va actualizando. | El valor inicial. Un acumulador de productos inicializado a 0 da siempre 0. |
| Búsqueda | Recorre hasta encontrar algo y sale. | Qué pasa si no lo encuentra; y si sale con break, en qué queda el índice. |
| Máximo o mínimo | Guarda el mejor visto hasta ahora. | La inicialización: con el primer elemento, no con cero —si todos son negativos, el máximo nunca sería cero—. |
| Centinela | Lee hasta encontrar un valor especial que marca el final. | Que el centinela no se procese como si fuera un dato más. |
Y dos errores clásicos con nombre propio. El error de uno (off-by-one) es recorrer una posición de más o de menos: pasa siempre en los extremos, al confundir «menor que n» con «menor o igual que n» en un vector cuyos índices llegan a n−1. Y el bucle que no entra nunca, que no es un fallo del bucle sino de la condición inicial, y que un while permite y un do…while no.
6.4. Bucles anidados
Un bucle dentro de otro ejecuta el interno entero por cada pasada del externo: si el externo da n vueltas y el interno m, el cuerpo se ejecuta n × m veces. Es la estructura con la que se recorre una matriz —el externo por filas, el interno por columnas— y también la razón por la que un algoritmo con dos bucles anidados sobre los mismos datos tiene coste cuadrático.
Dos avisos: cada bucle necesita su propia variable de control —reutilizar la misma es un error garantizado—, y un break dentro del interno solo sale del interno, no de los dos.
7. Recursividad
Un subprograma es recursivo cuando se llama a sí mismo. Para que funcione necesita dos partes, y si falta la primera el programa no se cuelga: revienta.
- Caso base: la situación en la que la función devuelve un resultado sin volver a llamarse. Es la condición de parada.
- Caso recursivo: la llamada a sí misma con un problema estrictamente menor, de modo que la sucesión de llamadas se acerque al caso base.
Cada llamada pendiente ocupa un marco en la pila de llamadas, con sus parámetros y sus variables locales. Si no hay caso base —o no se alcanza— la pila se agota y salta el desbordamiento de pila (stack overflow). Esa es la diferencia práctica frente a un bucle infinito, que no consume memoria y sigue girando indefinidamente.
| Recursividad | Iteración | |
|---|---|---|
| Legibilidad | Mayor en problemas naturalmente recursivos (árboles, Hanoi) | Mayor en recorridos lineales |
| Memoria | Consume pila, una entrada por llamada | Constante |
| Velocidad | Menor: cada llamada tiene coste | Mayor |
| Riesgo | Desbordamiento de pila | Bucle infinito |
Dos matices que separan al que ha estudiado: la recursividad es directa si la función se llama a sí misma e indirecta o mutua si A llama a B y B llama a A. Y se llama recursividad de cola a aquella en la que la llamada recursiva es la última operación; algunos compiladores la convierten automáticamente en un bucle y entonces no consume pila.
⚠ Cuidado con el Fibonacci recursivo ingenuo: es el ejemplo de manual y a la vez el ejemplo de lo que no hay que hacer, porque recalcula los mismos valores una y otra vez y su coste crece exponencialmente. Todo algoritmo recursivo puede escribirse de forma iterativa, y al revés.
7.1. Trazar una recursión: el factorial, paso a paso
La recursión se traza en dos tiempos, y ahí está lo que confunde: primero bajan las llamadas apilándose sin devolver nada, y después suben los resultados. Con el factorial de 4, definido como «factorial(0) = 1» y «factorial(n) = n × factorial(n−1)»:
| Fase | Qué ocurre | Estado de la pila |
|---|---|---|
| Bajada | factorial(4) necesita factorial(3) | 4 |
| Bajada | factorial(3) necesita factorial(2) | 4, 3 |
| Bajada | factorial(2) necesita factorial(1) | 4, 3, 2 |
| Bajada | factorial(1) necesita factorial(0) | 4, 3, 2, 1 |
| Caso base | factorial(0) devuelve 1 sin llamar a nadie | 4, 3, 2, 1, 0 |
| Subida | 1 × 1 = 1 · luego 2 × 1 = 2 · luego 3 × 2 = 6 · luego 4 × 6 = 24 | se vacía |
Fíjate en que nada se multiplica en la bajada: las cinco llamadas están vivas a la vez, cada una esperando el resultado de la siguiente. Eso es lo que consume pila, y por eso la profundidad de la recursión importa tanto como el número total de llamadas.
7.2. Los tres ejemplos canónicos
- Factorial: una sola llamada por nivel, profundidad n. Es el ejemplo limpio.
- Fibonacci: dos llamadas por nivel, y el árbol de llamadas se dispara. Calcular el término 40 de forma ingenua supone más de mil millones de llamadas, muchas repetidas. La cura es guardar los resultados ya calculados —memoización— o pasar a un bucle, que lo resuelve con dos variables.
- Torres de Hanói: el caso en el que la recursión gana de calle, porque la versión iterativa es mucho menos legible. Mover n discos exige 2n − 1 movimientos: con 3 discos, 7; con 10, 1.023. Es el ejemplo de que una solución elegante puede seguir siendo exponencial.
Y una regla práctica para el examen: si te preguntan qué devuelve una función recursiva, empieza por el caso base y sube, no al revés. Bajar desde el caso general lleva a perderse; subir desde el caso base son tres líneas de cálculo.
8. Procedimientos, funciones y parámetros
8.1. La diferencia, que es una pregunta directa
| Función | Procedimiento | |
|---|---|---|
| Devuelve valor | Sí, asociado a su nombre | No (o solo por sus parámetros) |
| Cómo se invoca | Puede aparecer como operando de una expresión | Como una sentencia independiente |
| Sentencia de retorno | La necesita para devolver el valor | No está obligado a ejecutar ninguna |
🚨 La pregunta de 2024, en dos modelos, pedía la afirmación correcta sobre funciones y procedimientos, y era: «los parámetros por referencia se pueden emplear como parámetros de entrada/salida». Los tres distractores fallaban cada uno por un motivo distinto, y merecen desmontarse:
- «Una llamada a un procedimiento puede aparecer como operando de una expresión» → falso: eso es propio de la función, que es la que devuelve valor.
- «El paso por referencia envía una copia del valor, por lo que no se podrá alterar la variable» → falso: eso describe el paso por valor. Con el nombre cambiado.
- «Todo procedimiento debe ejecutar una sentencia de return» → falso: precisamente el procedimiento es el que no está obligado.
8.2. Parámetros
Primero, dos palabras que hay que distinguir: el parámetro formal es el que aparece en la declaración del subprograma; el parámetro real o argumento es el valor concreto que se pasa en la llamada. Se habla por eso de parámetros formales y reales.
| Forma de paso | Qué se envía | ¿Puede modificar la variable original? |
|---|---|---|
| Por valor | Una copia del valor | No. Los cambios se pierden al volver. |
| Por referencia | La dirección de la variable | Sí. Por eso sirve como entrada/salida. |
| Por valor-resultado | Copia al entrar y copia de vuelta al salir | Sí, pero solo al terminar |
| Por nombre | La expresión, que se evalúa cada vez que se usa | Sí, y con efectos poco intuitivos |
Y según su papel, los parámetros son de entrada (solo se leen), de salida (solo se escriben) o de entrada/salida (se leen y se escriben). Estos últimos exigen paso por referencia, que es justo lo que afirmaba la respuesta correcta del examen.
⚠ El matiz que confunde a casi todo el mundo: en Java todo se pasa por valor, también los objetos. Lo que ocurre es que en el caso de un objeto el valor que se copia es la referencia, así que se puede modificar el contenido del objeto pero no hacer que la variable del llamante apunte a otro objeto. Decir «en Java los objetos se pasan por referencia» es una simplificación que en un examen puede costar cara.
Otras posibilidades habituales: sobrecarga (varios subprogramas con el mismo nombre y distinta lista de parámetros), valores por defecto y número variable de argumentos.
8.3. Valor frente a referencia, con el ejemplo delante
La diferencia se ve en tres líneas. Supón un subprograma que recibe un número y le suma diez, y una variable del llamante que vale 5:
- Por valor: el subprograma recibe una copia que vale 5, la deja en 15 y al terminar la copia desaparece. La variable del llamante sigue valiendo 5.
- Por referencia: el subprograma recibe la dirección de la variable, así que suma sobre ella misma. Al terminar, la variable del llamante vale 15.
De ahí sale toda la terminología: un parámetro que solo se lee es de entrada y puede ir por valor; uno que devuelve información es de salida y necesita referencia; y uno que hace las dos cosas —entra con un valor y sale con otro— es de entrada/salida y exige referencia. Esa es exactamente la frase que el tribunal dio por correcta.
El precio del paso por referencia son los efectos laterales: un subprograma que modifica lo que recibe cambia el estado del programa más allá de su propio ámbito, y eso hace el código más difícil de razonar y de probar. La disciplina razonable es pasar por valor por defecto y usar referencia solo cuando haga falta devolver algo o cuando copiar sea caro.
8.4. Ámbito y tiempo de vida
Dos conceptos que se confunden y que son independientes:
- El ámbito es la zona del código desde la que se ve una variable. Una variable local solo se ve dentro de su subprograma; una global, desde todo el programa. Los lenguajes modernos añaden el ámbito de bloque: la variable solo existe entre las llaves donde se declaró —es la diferencia entre
letyvaren JavaScript—. - El tiempo de vida es cuánto dura en memoria. Una local nace al entrar en el subprograma y muere al salir; una global dura todo el programa; y una estática es local en ámbito pero conserva su valor entre llamadas, porque su tiempo de vida es el del programa entero.
Cuando una variable local tiene el mismo nombre que una global, la local oculta a la global dentro de su ámbito, y a eso se le llama ocultación. Es legal y es una fuente inagotable de errores; por eso el consejo permanente es reducir las globales al mínimo y declarar cada variable en el ámbito más estrecho posible.
9. Vectores y registros
9.1. El array o vector
La definición que el tribunal ha dado por buena dos veces, literal:
«Un array o vector es un tipo de dato estructurado que permite almacenar un conjunto de datos homogéneo donde cada elemento se almacena de forma consecutiva en memoria.»
Las tres palabras que la sostienen son estructurado, homogéneo y consecutivo. Y esa última no es un adorno: por estar contiguos en memoria, la dirección de cualquier elemento se calcula con una multiplicación —dirección base más índice por tamaño—, y por eso el acceso por índice es de coste constante. Es la ventaja del array frente a la lista enlazada.
Lo que hay que tener claro además:
- El índice suele empezar en 0 (C, C++, Java, C#, Python, JavaScript) y no en 1, así que un array de n elementos tiene índices de 0 a n−1. Salirse produce un error de índice fuera de rango.
- Tamaño fijo una vez creado. Las colecciones que crecen (listas, vectores dinámicos) son otra cosa y suelen estar en la biblioteca, no en el lenguaje.
- Un array de dos dimensiones es una matriz; se recorre con dos bucles anidados.
🚨 Los arrays se declaran de forma distinta en cada lenguaje, y el tribunal lo aprovecha. La declaración correcta de un array de enteros en C# cayó en 2019: int[] arrayEnteros = new[] {1, 2, 3};. Los corchetes van pegados al tipo, no al nombre de la variable —eso es sintaxis de C y de Java, y en C# no compila—, y detrás de new van corchetes, no paréntesis.
9.1 bis. Los cuatro recorridos que hay que saber escribir
Un vector se manipula siempre con las mismas cuatro rutinas, y todas caben en un bucle contador:
| Rutina | Cómo se hace | El detalle que se falla |
|---|---|---|
| Sumar | Acumulador a 0 y sumar cada elemento. | Inicializar a 0, no al primer elemento (lo contaría dos veces). |
| Buscar | Recorrer comparando y salir al encontrar. | Devolver una marca —típicamente −1— si no está. |
| Máximo | Guardar el primero como candidato y comparar con el resto. | Inicializar con el primer elemento, nunca con cero. |
| Contar | Contador a 0 e incrementar cuando se cumple la condición. | No confundir el contador con el índice del bucle. |
Sobre la búsqueda conviene distinguir dos casos, porque el coste cambia radicalmente: la búsqueda secuencial recorre el vector de principio a fin y sirve siempre, mientras que la búsqueda binaria parte el vector por la mitad en cada paso y encuentra en muy pocos intentos, pero exige que el vector esté ordenado. Con un millón de elementos, la secuencial puede necesitar un millón de comparaciones y la binaria unas veinte.
9.1 ter. Matrices
Una matriz es un array de dos dimensiones y se recorre con dos bucles anidados: el externo por filas y el interno por columnas. En memoria sigue siendo lineal, y hay dos formas de aplanarla —por filas, que es lo que hacen C, C++, Java, C# y Python, y por columnas, que es lo de Fortran—. La consecuencia práctica: recorrer una matriz grande en el mismo orden en que está guardada es sensiblemente más rápido, porque aprovecha la caché.
Y el detalle de sintaxis que separa lenguajes: en Java y C# una matriz se declara con dos pares de corchetes o con una coma dentro de uno, mientras que en Python lo habitual es una lista de listas, con la particularidad de que las filas pueden tener longitudes distintas —lo que en Java se llama array irregular o jagged—.
9.2. El registro
El registro (struct, o record) agrupa datos de tipos distintos bajo un solo nombre, cada uno en su campo, y se accede con el operador punto. Es el complemento del array: el array agrupa muchos datos iguales, el registro agrupa pocos datos distintos. La estructura de trabajo más habitual es la combinación de los dos, un array de registros, que es exactamente lo que en una base de datos sería una tabla.
9.3. Cadenas, inmutabilidad y el bucle que concatena
En Java, C# y Python las cadenas son inmutables: cada vez que «modificas» una cadena en realidad se crea otra nueva. Concatenar dentro de un bucle crea, por tanto, un objeto por vuelta, y el rendimiento se hunde con cadenas grandes.
🚨 La pregunta de 2019: «en un módulo Java se detecta un bucle que concatena una cantidad muy elevada de cadenas, penalizando el rendimiento; ¿qué clase de java.lang habría que usar?». Respuesta: StringBuilder, que mantiene un búfer modificable y evita crear objetos intermedios. Los distractores —StringConcat, StringConstruct, StringCreator— son nombres inventados con toda la intención. Conviene saber además que existe StringBuffer, que hace lo mismo pero es sincronizada y por tanto más lenta: se usa solo si varios hilos tocan la misma cadena.
10. Estructura de un programa
Un programa, en casi cualquier lenguaje, tiene tres zonas: una cabecera con el nombre y las importaciones de bibliotecas, una zona de declaraciones de constantes, tipos y variables, y un cuerpo con las instrucciones. La palabra que trae cada lenguaje para importar cambia, y también se pregunta:
| Lenguaje | Cómo se trae una biblioteca |
|---|---|
| Python | import biblioteca, y con alias, import biblioteca as alias |
| Java | import paquete.Clase; |
| C y C++ | #include |
| C# | using |
🚨 Cayó en los dos modelos de 2026: cargar TensorFlow en Python se escribe import tensorflow as tf. Los distractores usaban la palabra de otro lenguaje —include de C, using de C#— y un load inventado. Es una pregunta que se contesta sabiendo qué palabra pertenece a qué lenguaje, no sabiendo TensorFlow.
10.1. Programación estructurada
El teorema del programa estructurado (Böhm y Jacopini, 1966) demuestra que cualquier algoritmo puede escribirse combinando solo tres estructuras: secuencia, selección e iteración. De ahí sale la disciplina de prescindir del salto incondicional (goto) y la regla de que todo bloque tiene una entrada y una salida.
Y las prácticas que hacen legible el resultado: nombres significativos, un convenio de escritura constante, sangrado sistemático, comentarios que expliquen el porqué y no el qué, subprogramas cortos y con una sola responsabilidad, y evitar los números mágicos sustituyéndolos por constantes con nombre. La complejidad ciclomática mide el número de caminos independientes de un subprograma y sirve como aviso: cuanto más alta, más pruebas hacen falta y más difícil es de mantener.
10.1 bis. Un programa entero, comentado
Reunidas todas las piezas del tema, así se lee un programa cualquiera. En pseudocódigo, para que sirva con independencia del lenguaje:
PROGRAMA NotasOpositores
// 1. Declaraciones
CONSTANTE APROBADO = 25
VECTOR notas[100] DE real
ENTERO i, cuantas, aprobados
REAL suma, media
// 2. Entrada de datos, con centinela
cuantas ← 0
REPETIR
leer(nota)
SI nota ≥ 0 ENTONCES notas[cuantas] ← nota; cuantas ← cuantas + 1 FINSI
MIENTRAS nota ≥ 0 Y cuantas < 100
// 3. Proceso: acumulador y contador
suma ← 0; aprobados ← 0
PARA i DESDE 0 HASTA cuantas − 1 HACER
suma ← suma + notas[i]
SI notas[i] ≥ APROBADO ENTONCES aprobados ← aprobados + 1 FINSI
FINPARA
// 4. Salida, protegida de la división por cero
SI cuantas > 0 ENTONCES media ← suma / cuantas; escribir(media, aprobados)
SINO escribir("No hay datos") FINSI
FINPROGRAMA
Merece la pena señalar las cinco decisiones que hay ahí dentro, porque son las que el examen ataca una por una:
- Se usa un repetir…mientras para la lectura, porque hay que leer al menos una vez antes de saber si el usuario ha terminado.
- La condición de salida combina el centinela (una nota negativa) con el tope del vector, y en ese orden: gracias al cortocircuito, si el centinela ya decide, no se comprueba el tope.
- El bucle de proceso recorre de 0 a cuantas − 1, no hasta 100: el vector tiene 100 huecos pero solo cuantas están llenos, y ese es el error de uno más común.
- El acumulador y el contador se inicializan a cero antes del bucle, no dentro.
- Y la división se protege: si no hay datos no se divide. Sin esa comprobación, ejecutar el programa sin escribir nada provoca un error de ejecución.
10.2. Modularidad: cohesión y acoplamiento
Partir un programa en módulos no es cuestión de gusto: se mide con dos criterios que van en direcciones opuestas y que conviene saber nombrar.
- La cohesión mide cuánto tienen que ver entre sí las cosas que hay dentro de un módulo. Cuanto más alta, mejor: un módulo que hace una sola cosa y la hace entera es fácil de entender, de probar y de reutilizar.
- El acoplamiento mide cuánto depende un módulo de los demás. Cuanto más bajo, mejor: si para cambiar un módulo hay que tocar otros cinco, el diseño está mal repartido.
La regla, que se pregunta tal cual: alta cohesión y bajo acoplamiento. Y una consecuencia práctica: pasar información por parámetros acopla menos que compartir variables globales, que es la forma más fuerte —y peor— de acoplamiento.
10.3. Pseudocódigo y ordinogramas
Antes de escribir en un lenguaje concreto, el algoritmo se describe con una notación independiente. Hay dos clásicas, y las dos aparecen en el temario del bloque II:
- El pseudocódigo es texto: usa las palabras de las estructuras de control —mientras, si, repetir— en lenguaje natural, con sangría y sin sintaxis de ningún lenguaje real.
- El ordinograma o diagrama de flujo es gráfico, y su simbología es fija: óvalo para inicio y fin, rectángulo para un proceso, romboide para entrada y salida, rombo para una decisión y flechas para el flujo. El diagrama de Nassi-Shneiderman es una variante en cajas encajadas que, por construcción, no permite dibujar un salto incondicional: obliga a programar estructurado.
11. Errores y excepciones
Los errores se clasifican por cuándo aparecen, y saber en qué categoría cae uno es la mitad de la respuesta en las preguntas del tipo «¿qué ocurrirá al ejecutar este código?».
| Tipo | Cuándo salta | Ejemplo |
|---|---|---|
| Léxico o sintáctico | Al compilar, y el programa ni se genera | Falta un paréntesis o un punto y coma |
| Semántico o de tipos | Al compilar, en lenguajes de tipado estático | Llamar a un método sobre un tipo primitivo |
| De ejecución | Al ejecutar, y aborta el programa si nadie lo trata | División por cero, índice fuera de rango, referencia nula |
| Lógico | Nunca salta: el programa funciona y da mal el resultado | Un bucle infinito o un acumulador mal inicializado |
🚨 Cayó en los dos modelos de 2026: una clase Java con private int idAlumno; y un método que hace idAlumno.equals(id). Al ejecutar javac se produce error de compilación, porque el tipo int no tiene método equals(). Los distractores proponían que compilaría bien, que el error sería de ejecución y no de compilación, o que equals() solo funciona a partir de cierta versión de Java. La clave: en Java los tipos primitivos no son objetos y por tanto no tienen métodos; el error lo detecta el compilador, no la máquina virtual.
El tratamiento de excepciones es el mecanismo para que un error de ejecución no tumbe el programa. Tiene tres piezas, y el examen puede preguntar por la tercera:
- El bloque try encierra el código que puede fallar.
- Uno o varios catch capturan cada tipo de excepción. Se ordenan de la más específica a la más general: al revés, la general se lo tragaría todo.
- El bloque finally se ejecuta siempre, haya habido excepción o no, e incluso si el try hizo un return. Es el sitio donde se cierran ficheros y conexiones.
Java distingue además entre excepciones comprobadas —el compilador te obliga a capturarlas o a declararlas— y no comprobadas, que derivan de errores de programación y no obligan a nada. C# y Python no hacen esa distinción.
12. Los seis lenguajes que este tribunal pregunta
El examen no pide dominar ninguno: pide reconocer una peculiaridad de cada uno. Estas son las que han caído, con la trampa al lado.
11.1. Java
- Las clases abstractas (
abstract) pueden no estar implementadas o estarlo de forma incompleta, y no puede instanciarse; sí puede extenderse. (2019) - Las clases finales (
final) no pueden heredarse. Para impedir que se herede de una clase llamada Ejercicio se escribefinal class Ejercicio. (2019) - Los nombres de variable pueden contener el carácter
$(y el guion bajo), no pueden empezar por un dígito y no pueden ser una palabra reservada comostatic. Los comentarios de varias líneas empiezan por/*, no por//. (2023) - Los tipos primitivos no tienen métodos: escribir
idAlumno.equals(id)sobre unintda error de compilación, no de ejecución. Para compararlos se usa el operador de igualdad. (2026)
11.2. Python
- Los bloques se delimitan por sangría. (2019)
- Acceso anidado a diccionarios y listas: con
opositor = {'nombre': 'Sandra Gomez', 'notas': [27, 36, 12]}, el segundo elemento de las notas esopositor['notas'][1]. Dos claves: el diccionario se indexa con la clave entre comillas, y la lista empieza en 0, así que el «segundo» ejercicio es el índice 1. (2019) - Importar con alias:
import tensorflow as tf. (2026) - Bibliotecas que ha nombrado: Keras para aprendizaje automático y Django como marco de desarrollo web. Los marcos de Python son materia recurrente y conviene no confundirlos con las bibliotecas de cálculo. Numpy y Pip aparecieron como distractores: Numpy es cálculo numérico y Pip es el gestor de paquetes, no una biblioteca de aprendizaje. (2019)
11.3. JavaScript
==convierte tipos;===no.123 == '123'es verdadero. (2026)lettiene alcance de bloque;var, de función. Una variable declarada sin valor valeundefined. (2026)- No forma parte de la plataforma Java. (2026)
- Es el único de los seis que el navegador ejecuta de forma nativa, sin complementos ni máquina virtual añadida. (2026)
- El operador de propagación —tres puntos delante de un array— despliega sus elementos dentro de otro: con dos arrays produce uno nuevo con el contenido de ambos, sin filtrar ni ordenar. Cayó sobre TypeScript en 2026.
- jQuery no es un lenguaje de programación: es una biblioteca de JavaScript. Y React es otra biblioteca de JavaScript, para interfaces de usuario. (2026)
- La versión concreta que citó el enunciado de 2026 fue ECMAScript 2022; ECMAScript es la especificación normalizada del lenguaje y publica una edición al año.
11.4. C#
- Array de enteros:
int[] arrayEnteros = new[] {1, 2, 3};. (2019) - Tipo anulable:
int? Telefono. (2024) - .NET MAUI es un marco multiplataforma para crear aplicaciones móviles y de escritorio nativas con C# y XAML —no solo Android, y no es una herramienta de pruebas—. (2024)
- ADO.NET es el componente de la plataforma .NET para comunicación con bases de datos. (2023)
11.5. C y C++
- Son de tercera generación. (2023)
- La declaración
int vector2[] = {1,2,3,4,10,9,80,70,19};es correcta en C++: cuando se inicializa en la misma línea, no hace falta indicar el tamaño. (2023) - do…while: ejecuta primero, comprueba después. (2019)
11.6. F#
- La palabra clave
letenlaza un nombre a un valor o a una función. (2024) - El tipo
sbytees un entero con signo de 8 bits —el que va sin signo esbyte—, los comentarios de varias líneas se abren con(*y se cierran con*), ylet immutable x = 1no es válido: en F# las asociaciones ya son inmutables por defecto y la palabra immutable no existe. (2024)
11.1. El manejo de excepciones, bloque a bloque
El esquema try / catch / finally se
pregunta por su comportamiento, no por su sintaxis:
- try encierra el código que puede fallar.
- catch captura un tipo de excepción. Puede haber varios, y se evalúan en orden: por eso el catch del tipo más general debe ir el último, o los específicos de detrás no se alcanzan nunca.
- 🚨 finally se ejecuta SIEMPRE: haya
excepción o no, se capture o no, e incluso si el try lleva un
return. Es el sitio donde se cierran ficheros y conexiones, y es la pregunta que más se falla.
En Java conviene distinguir las comprobadas (checked),
que el compilador obliga a capturar o a declarar con throws, de las
no comprobadas (unchecked, las que descienden de
RuntimeException), que no lo exigen. Y por debajo de todas,
Error señala fallos del entorno de los que no se espera que el programa
se recupere.
No todos los lenguajes tienen excepciones. En C el manejo clásico es por código de retorno y variable de error: la función devuelve un valor convenido y el programa lo comprueba. Es más rápido y más propenso al olvido, porque nada obliga a mirar el resultado.
11.2. Los tres paradigmas, en una frase cada uno
- Imperativo o estructurado: se describe cómo, paso a paso, con secuencia, selección e iteración. C y Pascal.
- Orientado a objetos: el programa es un conjunto de objetos que encapsulan datos y comportamiento y se mandan mensajes. Java y C#.
- Declarativo: se describe qué se quiere y no cómo obtenerlo. Incluye el funcional (Haskell, y las expresiones lambda que hoy llevan casi todos), el lógico (Prolog) y los lenguajes de consulta como SQL.
🚨 SQL es declarativo, y esa es la pregunta: se le dice qué filas se quieren, no cómo recorrerlas. Un lenguaje moderno rara vez es de un solo paradigma —Java tiene lambdas y Python es multiparadigma—, así que la respuesta correcta suele hablar de paradigma predominante.
11.3. Cómo se clasifica un lenguaje: los tres ejes
El tribunal pregunta «cuál de los siguientes es…» y la respuesta depende de saber en qué eje se está moviendo. Son tres y son independientes entre sí:
- Compilado o interpretado. El compilador traduce el programa entero a código máquina antes de ejecutarlo; el intérprete lo traduce y ejecuta línea a línea. Java y C# están en medio: compilan a un código intermedio (bytecode, CIL) que ejecuta una máquina virtual, a menudo con compilación JIT en tiempo de ejecución.
- Tipado estático o dinámico. Estático es que el tipo se conoce y se comprueba en compilación (Java, C#); dinámico, que se resuelve en ejecución (Python, JavaScript).
- Tipado fuerte o débil. Fuerte es que el lenguaje no convierte tipos por su cuenta y exige conversión explícita; débil es que hace conversiones implícitas.
🚨 Los dos últimos ejes no son lo mismo y ahí está la pregunta: Python es de tipado dinámico y fuerte —resuelve el tipo en ejecución pero no suma un número y una cadena—, mientras que JavaScript es dinámico y débil. Decir «dinámico, luego débil» es el error.
11.4. Los tres tipos de error de un programa
- De sintaxis: el código incumple las reglas del lenguaje. Lo caza el compilador y el programa ni llega a ejecutarse.
- De ejecución: la sintaxis es válida pero algo falla al correr —dividir por cero, abrir un fichero que no existe—. Es el terreno de las excepciones.
- Lógico o semántico: el programa se ejecuta sin quejarse y da un resultado equivocado. Es el más caro, porque ninguna herramienta lo detecta: solo lo encuentran las pruebas.
13. Lo que ya ha preguntado el tribunal
| Lo preguntado | Veces | La respuesta |
|---|---|---|
Trazar while con incremento y decremento | 3 | Es un bucle infinito |
| Funciones y procedimientos: afirmación correcta | 2 | Los parámetros por referencia sirven como entrada/salida |
| Array o vector: afirmación verdadera | 2 | Estructurado, homogéneo y consecutivo en memoria |
| Componente que compila el intermedio en ejecución | 2 | El JIT |
| Tipo anulable en C# | 2 | int? Telefono |
| Importar una biblioteca en Python | 2 | import tensorflow as tf |
Comparar un int con equals() en Java | 2 | Error de compilación: los primitivos no tienen métodos |
| Qué es .NET MAUI | 2 | Marco multiplataforma con C# y XAML |
| Compilador, intérprete y transpilador: la falsa | 1 | El transpilador NO hace de intérprete |
| Bucle do…while en C++ | 1 | Se ejecuta antes de evaluar la condición |
| Bloque de un if en Python | 1 | La sangría |
| Traza de una función JavaScript con acumulador | 1 | Devuelve 9 |
| Coma flotante de doble precisión (IEEE 754) | 1 | Aproximación de 64 bits |
| JavaScript: afirmación incorrecta | 1 | 123 == '123' devuelve verdadero, no falso |
Clase abstract en Java | 1 | Puede estar sin implementar o implementada a medias |
| Impedir la herencia de una clase Java | 1 | final class Ejercicio |
| Concatenar cadenas en un bucle Java | 1 | StringBuilder |
| Declarar un array de enteros en C# | 1 | int[] a = new[] {1, 2, 3}; |
| Nombres de variable válidos en Java | 1 | Pueden contener $ |
| Lenguaje F#: afirmación correcta | 1 | let enlaza un nombre a un valor o función |
| Acceso anidado a diccionario y lista en Python | 1 | opositor['notas'][1] |
| Biblioteca de aprendizaje automático para Python | 1 | Keras |
| Marco web para Python | 1 | Django |
Cómo estudiar este tema, en una frase. Los otros temas se aprueban recordando; este se aprueba trazando. Ante un fragmento, haz siempre lo mismo: identifica la variable de la condición, monta una tabla con una fila por pasada y sigue el código con el dedo hasta que la condición falle. Y ten a mano las cuatro parejas que el tribunal intercambia sin avisar —función/procedimiento, valor/referencia, while/do…while e igualdad estricta/no estricta—, porque más de la mitad de sus distractores salen de cambiar una por la otra.
¿Te lo sabes? 🐂
Cien preguntas de este tema, escritas al estilo del cuestionario del INAP y contrastadas con los nueve exámenes oficiales que tenemos resueltos. Crea tu cuenta gratis para guardar tu progreso y repasar tus falladas. Gratis y siempre lo será.
