100 preguntas de entrevista técnica FullStack Java y Angular
Esta entrada recoge 100 preguntas de entrevista técnica de FullStack Java y Angular con la respuesta de cada una. Los temas cubren bases de datos, Java, programación orientada a objetos, Spring, Angular, TypeScript, arquitectura de software, DevOps y testing.
Las preguntas van numeradas de la 1 a la 100 y agrupadas en bloques por tema, así que sirven para repasar de corrido o para buscar un punto concreto.
Bases de datos: triggers y stored procedures
1. ¿Qué diferencia hay entre un trigger y un stored procedure?
Respuesta: Un trigger es un bloque de lógica que la base de datos ejecuta automáticamente cuando ocurre un evento definido, como insertar, actualizar o eliminar registros en una tabla, o en determinados eventos de la propia base de datos. Un stored procedure o procedimiento almacenado es una rutina guardada en la base de datos que normalmente se ejecuta cuando una aplicación, usuario u otra rutina la invoca de manera explícita. La diferencia central es que el trigger responde a un evento; el procedimiento responde a una llamada, aunque un trigger también puede invocar un procedimiento almacenado.
2. Si un procedimiento se invoca desde la aplicación, ¿no sería también un trigger, es decir, algo automatizado?
Respuesta: No necesariamente, porque automatización y trigger no significan lo mismo. Una aplicación puede llamar automáticamente a un procedimiento como parte de su flujo de trabajo, pero sigue siendo una invocación explícita desde el punto de vista de la base de datos: el procedimiento no se dispara por sí solo al ocurrir un evento sobre una tabla. Un trigger, en cambio, queda asociado al evento y la base de datos lo ejecuta sin que la aplicación tenga que llamarlo directamente.
3. ¿En qué caso utilizarías un trigger y en qué caso utilizarías un stored procedure?
Respuesta: Utilizaría un trigger cuando una acción deba ejecutarse siempre que ocurra un evento concreto en la base de datos, independientemente de qué aplicación o usuario haya provocado ese evento; por ejemplo, registrar automáticamente en una tabla de auditoría ciertos cambios. Utilizaría un procedimiento almacenado para una operación que tenga un propósito definido y deba ejecutarse bajo petición, como cerrar un periodo contable o procesar una operación con varios pasos. Evitaría colocar demasiada lógica oculta en triggers, porque puede dificultar la comprensión, las pruebas y el diagnóstico de efectos secundarios.
Java: tipado fuerte, tipos primitivos y wrappers
4. ¿Por qué Java es fuertemente tipado y qué beneficios tiene el tipado fuerte frente a los lenguajes no fuertemente tipados?
Respuesta: Java es fuertemente tipado porque cada expresión y variable tiene un tipo definido, y el lenguaje restringe qué operaciones pueden realizarse con ese tipo; además, es estáticamente tipado porque muchas incompatibilidades se detectan durante la compilación. Esto ayuda a detectar errores antes de ejecutar el programa, hace más explícitos los contratos entre métodos y facilita el mantenimiento y las herramientas de desarrollo. En lenguajes dinámicos o con reglas de conversión más permisivas puede haber más flexibilidad, pero ciertos errores de tipos pueden aparecer hasta el tiempo de ejecución; la diferencia no significa que un lenguaje sea automáticamente mejor que otro.
5. ¿Cuáles son los tipos de datos en Java?
Respuesta: Java tiene ocho tipos primitivos: byte, short, int, long, float, double, char y boolean. Además, existen los tipos de referencia, que incluyen clases, interfaces, arreglos, enumeraciones y otros tipos de objetos; las variables de referencia almacenan una referencia a un objeto o pueden tener el valor null. Por eso, al hablar de tipos en Java conviene distinguir entre los primitivos y los tipos de referencia.
6. ¿Cuál es la diferencia entre int, long, float, double, char y boolean?
Respuesta: int representa números enteros de 32 bits y long enteros de 64 bits, por lo que long permite un rango mucho mayor. float representa números de punto flotante de 32 bits y double de 64 bits; double ofrece normalmente más precisión y es el tipo habitual para cálculos decimales generales, aunque ninguno representa todos los decimales exactamente. char almacena una unidad de código UTF-16 de 16 bits, no necesariamente un carácter Unicode completo en todos los casos, y boolean representa únicamente true o false; Java no especifica un tamaño de almacenamiento equivalente para boolean como sí lo hace para los demás tipos primitivos.
7. ¿Qué son los wrappers en Java?
Respuesta: Los wrappers, o clases envoltorio, son las clases que representan los tipos primitivos como objetos. Las correspondencias principales son Byte, Short, Integer, Long, Float, Double, Character y Boolean para byte, short, int, long, float, double, char y boolean, respectivamente. También existe Void como representación de void en ciertos contextos de reflexión, aunque no se utiliza como un wrapper ordinario para almacenar valores.
8. ¿Cuál es la diferencia entre los wrappers y los tipos de datos primitivos?
Respuesta: Un tipo primitivo almacena directamente un valor elemental y no es un objeto; su variable no puede ser null y no dispone de métodos propios como una instancia. Un wrapper es un objeto que representa ese valor, por lo que puede ser null, ofrece métodos y constantes, y puede utilizarse en APIs y colecciones que requieren referencias, como List<Integer>. Los wrappers tienen un coste adicional de objetos y pueden producir conversiones automáticas llamadas boxing y unboxing, así que no conviene sustituir indiscriminadamente los primitivos por wrappers.
9. ¿A qué se refiere que los wrappers son envoltorios?
Respuesta: Se les llama envoltorios porque proporcionan una representación orientada a objetos alrededor de un valor primitivo. Por ejemplo, Integer permite tratar un valor int como un objeto y utilizarlo en contextos donde Java exige referencias, además de ofrecer operaciones de conversión, comparación y consulta. El valor sigue siendo un número entero; lo que cambia es la forma en que se representa y se utiliza dentro del lenguaje y sus APIs.
Clases, constructores y encapsulamiento
10. ¿Qué es una clase, qué es un objeto y qué es un constructor, y cuál es la diferencia entre los tres?
Respuesta: Una clase es una definición que establece los atributos y comportamientos que tendrán sus instancias; un objeto es una instancia concreta creada a partir de esa definición, con su propio estado. Un constructor es un mecanismo especial de la clase que se ejecuta al crear una instancia y permite establecer su estado inicial. En resumen, la clase define, el objeto existe como ejemplar concreto y el constructor participa en su inicialización.
11. ¿Por qué el constructor inicializa las variables? ¿Por qué generalmente los constructores las inicializan vacías?
Respuesta: El constructor establece las condiciones iniciales del objeto para que nazca en un estado válido y coherente con las reglas de la clase. No es una buena práctica inicializar siempre las variables vacías: se deben asignar valores significativos, recibidos como parámetros o calculados, cuando el objeto los necesita. Java asigna valores predeterminados a los campos si no se inicializan expresamente, pero las variables locales deben recibir un valor antes de utilizarse; depender de valores predeterminados como null puede permitir objetos incompletos.
12. ¿Cuáles son los diferentes tipos de modificadores de acceso?
Respuesta: Java ofrece cuatro niveles de acceso: public, protected, package-private y private. Public permite el acceso desde cualquier lugar donde la clase sea visible; protected permite el acceso desde el mismo paquete y también desde subclases bajo las reglas del lenguaje; package-private, que se obtiene al no escribir modificador, permite el acceso dentro del mismo paquete; private limita el acceso a la propia clase. Estos modificadores controlan la visibilidad de clases, atributos, constructores y métodos según las reglas aplicables a cada elemento.
13. ¿Qué es el encapsulamiento y qué son los getters y los setters?
Respuesta: El encapsulamiento consiste en agrupar el estado y el comportamiento de un objeto y controlar cómo otras partes del programa pueden acceder o modificar ese estado. Los getters son métodos que permiten consultar un dato y los setters son métodos que permiten modificarlo, normalmente aplicando validaciones o reglas. No es obligatorio crear un getter y un setter para cada campo: exponer únicamente las operaciones que el objeto necesita ayuda a mantener un diseño más seguro y coherente.
14. ¿De qué protege el encapsulamiento?
Respuesta: Protege el estado interno de un objeto frente a modificaciones directas e incontroladas desde otras partes del sistema. Al limitar el acceso y ofrecer operaciones controladas, la clase puede validar valores, conservar invariantes y evitar que el objeto termine en un estado inválido. También reduce el acoplamiento, porque otros componentes dependen de una interfaz pública y no de los detalles internos de implementación.
15. En pocas palabras, ¿el encapsulamiento protege de las malas prácticas?
Respuesta: Ayuda a prevenir algunas malas prácticas, especialmente el acceso indiscriminado a los atributos y la modificación del estado sin validaciones, pero no garantiza por sí solo un buen diseño. Un programa puede estar encapsulado y aun así tener clases demasiado grandes, responsabilidades mezcladas o reglas de negocio mal ubicadas. El encapsulamiento es una herramienta para controlar el diseño, no una solución automática a todos los problemas de calidad.
Herencia, interfaces y métodos
16. ¿Cuál es la diferencia entre una clase abstracta y una interfaz?
Respuesta: Una clase abstracta sirve como base común para clases relacionadas y puede contener estado, constructores, métodos implementados y métodos abstractos; una clase Java solo puede extender directamente una clase, abstracta o no. Una interfaz define principalmente un contrato que distintas clases pueden implementar y una clase puede implementar varias interfaces. Las interfaces modernas también pueden incluir métodos default, static y private, mientras que las clases abstractas siguen siendo útiles cuando se necesita compartir estado o una implementación base.
17. ¿Podría decirse que una interfaz es más obligatoria?
Respuesta: Una interfaz establece un contrato que una clase concreta debe cumplir: si la clase la implementa, debe proporcionar la implementación de sus métodos abstractos, salvo que herede una implementación válida o que la propia clase sea abstracta. En ese sentido establece obligaciones explícitas, pero no es simplemente más obligatoria que una clase abstracta; ambas pueden exigir la implementación de métodos. La elección depende de si se necesita definir una capacidad compartida entre tipos distintos o reutilizar también estado y comportamiento común.
18. ¿Qué es la palabra super y qué relación tiene con la herencia?
Respuesta: super es una referencia especial que permite acceder a miembros de la superclase desde una subclase. Se utiliza, por ejemplo, para invocar un constructor de la clase padre o para llamar a una implementación de un método que ha sido sobrescrito en la clase hija. Está relacionada con la herencia porque permite trabajar explícitamente con la parte heredada del objeto.
19. ¿Podría decirse que super permite utilizar o acceder a la implementación de la clase padre desde la clase hija?
Respuesta: Sí, esa es una forma correcta de explicarlo, con la precisión de que el acceso sigue sujeto a los modificadores de visibilidad y a las reglas de herencia de Java. Si la clase hija sobrescribe un método, puede invocar la implementación heredada mediante super seguido del método correspondiente. También puede utilizar super para llamar al constructor de la superclase, normalmente como primera instrucción del constructor de la subclase.
20. ¿Todo método abstracto pertenece a una clase abstracta? Es decir, cuando defines una clase abstracta, ¿también defines métodos abstractos?
Respuesta: Todo método abstracto debe declararse dentro de un tipo que permita ese tipo de declaración, como una clase abstracta o una interfaz; no tiene que pertenecer exclusivamente a una clase abstracta. Además, una clase abstracta no está obligada a declarar métodos abstractos: puede ser abstracta aunque todos sus métodos tengan implementación, por ejemplo, para impedir que se creen instancias directas. Una interfaz también puede declarar métodos abstractos, aunque en ella no se utiliza la palabra abstract de manera obligatoria para esos métodos.
21. ¿Cuál es la diferencia entre un método abstracto y un método normal?
Respuesta: Un método abstracto declara una operación, pero no proporciona el cuerpo que la implementa; por eso, una subclase concreta debe implementar ese método o heredar una implementación válida. Un método normal sí contiene una implementación que puede ejecutarse directamente y que las subclases pueden reutilizar o sobrescribir. El método abstracto define qué comportamiento debe existir, mientras que el método normal define también cómo se realiza.
22. ¿Qué es un método static?
Respuesta: Un método static pertenece a la clase y no a una instancia específica, por lo que puede invocarse utilizando el nombre de la clase sin crear un objeto. No recibe una referencia this y, por tanto, no puede acceder directamente a los atributos o métodos de instancia; sí puede acceder directamente a otros miembros estáticos. Se utiliza para operaciones que no dependen del estado particular de un objeto, como ciertas funciones utilitarias o métodos de fábrica.
Polimorfismo: sobrecarga y sobrescritura
23. ¿Cuál es la diferencia entre sobrecarga y sobrescritura en el polimorfismo?
Respuesta: La sobrecarga ocurre cuando existen varios métodos con el mismo nombre, pero con listas de parámetros diferentes, por ejemplo, distinta cantidad o tipo de parámetros; el compilador determina cuál corresponde según la llamada. La sobrescritura ocurre cuando una subclase proporciona una implementación propia de un método heredado con una firma compatible, y la implementación ejecutada se decide en tiempo de ejecución según el tipo real del objeto. La sobrescritura es la forma más directamente asociada al polimorfismo dinámico entre una superclase o interfaz y sus implementaciones.
24. Si tenemos diferentes fórmulas para calcular el salario de un programador, un abogado y un contador, ¿aplicaría sobrecarga o sobrescritura?
Respuesta: Aplicaría principalmente sobrescritura si programador, abogado y contador son tipos distintos que comparten una operación como calcularSalario, pero cada uno implementa su propia fórmula. La aplicación podría trabajar con una referencia del tipo común y llamar al mismo método sin tener que preguntar qué profesión tiene el objeto. La sobrecarga no sería la elección adecuada solo porque cambie la fórmula: se utiliza cuando cambia la lista de parámetros de los métodos, no para seleccionar automáticamente una fórmula según la clase del objeto.
25. Si lo que cambia es la fórmula de cálculo del salario, ¿qué mecanismo utilizarías?
Respuesta: Utilizaría polimorfismo mediante sobrescritura: definiría una operación común para calcular el salario y permitiría que cada tipo de empleado aporte su propia implementación. Así, el código que solicita el cálculo no necesita conocer los detalles de cada fórmula. Si las fórmulas además deben cambiarse en tiempo de ejecución o combinarse sin depender de una jerarquía de herencia, también consideraría el patrón Strategy para encapsular cada fórmula como una estrategia intercambiable.
26. ¿Qué utilizarías en ese caso?
Respuesta: Para el escenario descrito utilizaría una interfaz o una clase abstracta con un método común de cálculo y una implementación específica para programadores, abogados y contadores. Si cada tipo representa una categoría estable del dominio, la sobrescritura con polimorfismo es suficiente; si una misma persona puede cambiar de esquema salarial o el esquema depende de una configuración, preferiría una estrategia de cálculo inyectable. La decisión depende de dónde reside la variación: en el tipo de empleado o en una regla de cálculo intercambiable.
27. ¿En qué caso utilizarías la sobrecarga? Dame un ejemplo.
Respuesta: Utilizaría sobrecarga cuando una misma operación tenga sentido con diferentes conjuntos de datos de entrada. Por ejemplo, una clase de cálculo podría ofrecer una operación para calcular un impuesto usando una tasa predeterminada y otra versión que reciba explícitamente la tasa; ambas expresan el mismo propósito, pero reciben parámetros distintos. También es habitual en constructores para permitir crear un objeto con diferentes cantidades de información inicial.
Colecciones, Streams y Lambda
28. ¿Cuáles son los diferentes tipos de colecciones en Java: List, Set, Queue y Map?
Respuesta: List representa una secuencia ordenada que permite elementos duplicados y acceso por posición; Set representa una colección que no admite elementos duplicados según sus reglas de igualdad; Queue organiza elementos para procesarlos siguiendo una política, como FIFO en muchas implementaciones, aunque existen colas con otras prioridades. Map almacena asociaciones de clave y valor, con claves únicas, pero técnicamente no es una subinterfaz de Collection: forma parte del marco de colecciones de Java y ofrece una estructura distinta.
29. ¿Qué es ArrayList y cómo se relaciona con List?
Respuesta: ArrayList es una implementación concreta de la interfaz List que utiliza internamente un arreglo redimensionable. Mantiene el orden de inserción, permite duplicados y ofrece acceso eficiente por índice; añadir elementos al final suele ser eficiente, aunque una ampliación interna puede requerir copiar elementos y las inserciones o eliminaciones intermedias pueden desplazar otros. Se utiliza cuando se necesita una lista general y el acceso por posición es frecuente.
30. ¿Qué son los Streams y las expresiones Lambda?
Respuesta: Stream es una API de Java que permite procesar secuencias de elementos mediante operaciones encadenadas, como filtrar, transformar, ordenar o agrupar, sin tener que escribir manualmente todos los ciclos. Una expresión lambda es una forma concisa de representar una función o comportamiento que puede pasarse como argumento, por ejemplo, la condición que debe cumplir un elemento. Los Streams no son colecciones ni almacenan necesariamente los datos: describen un flujo de procesamiento que normalmente se ejecuta cuando se aplica una operación terminal.
31. ¿Qué hacen filter, map, forEach y collect?
Respuesta: filter conserva únicamente los elementos que cumplen una condición; map transforma cada elemento en otro valor; forEach recorre los elementos para ejecutar una acción y es una operación terminal; collect reúne el resultado del flujo en una estructura u otra forma de resultado mediante un Collector. filter y map son operaciones intermedias y normalmente no ejecutan el procesamiento por sí solas, mientras que forEach y collect desencadenan la ejecución del pipeline.
32. ¿Cómo funcionan forEach y collect?
Respuesta: forEach consume el Stream y ejecuta una acción para cada elemento, por lo que suele utilizarse para efectos secundarios, como enviar información a un registro o realizar una acción externa; no devuelve una nueva colección. collect también consume el Stream, pero acumula sus elementos o resultados en una estructura, como una lista, un conjunto o un mapa, según el Collector utilizado. Después de una operación terminal, ese Stream ya no puede reutilizarse.
33. Si map es el que transforma los elementos, ¿por qué utilizaríamos forEach?
Respuesta: Se utiliza forEach cuando el objetivo no es producir una nueva secuencia transformada, sino ejecutar una acción por cada elemento, como imprimir un resultado, registrar un evento o enviar una notificación. Si necesito convertir cada elemento en otro y seguir procesando el resultado, utilizaría map; si necesito terminar el recorrido realizando una acción, utilizaría forEach. Conviene no usar forEach para transformaciones que podrían expresarse con map, porque se pierde claridad y se fomenta la modificación de variables externas.
Igualdad: equals, hashCode y hash
34. ¿Qué son equals() y hashCode()?
Respuesta: equals() define cuándo dos objetos deben considerarse lógicamente iguales, mientras que hashCode() devuelve un entero que se utiliza, entre otros fines, para organizar objetos en estructuras basadas en hash, como HashMap y HashSet. Java establece un contrato importante: si dos objetos son iguales según equals(), deben devolver el mismo hashCode(); el caso contrario no está garantizado, porque dos objetos diferentes pueden producir el mismo hash. Cuando una clase redefine equals(), normalmente también debe redefinir hashCode() de forma coherente.
35. ¿A qué se refiere el concepto de hash?
Respuesta: Un hash es el resultado de aplicar una función a una entrada para obtener un valor de tamaño fijo, que en Java se representa como un entero en el caso de hashCode(). En una tabla hash, ese valor ayuda a localizar una zona donde buscar o almacenar un elemento, haciendo que muchas operaciones sean eficientes en promedio. Dos entradas diferentes pueden producir el mismo hash, situación conocida como colisión, por lo que el hash no demuestra por sí solo que dos objetos sean iguales.
36. ¿El hash lo define el propio lenguaje Java?
Respuesta: Java define el método hashCode() en la clase base Object y establece el contrato que deben respetar sus implementaciones, pero no obliga a que todas las clases utilicen una fórmula idéntica. Si una clase no sobrescribe hashCode(), hereda la implementación de Object, cuya relación exacta con la identidad del objeto depende de la implementación de la JVM y no debe tratarse como un identificador permanente. Las clases que necesitan igualdad lógica deben definir una estrategia coherente para equals() y hashCode().
Los cuatro pilares de la POO
37. ¿Cuáles son los cuatro pilares de la programación orientada a objetos?
Respuesta: Los cuatro pilares comúnmente reconocidos son abstracción, encapsulamiento, herencia y polimorfismo. La abstracción representa las características esenciales de un concepto; el encapsulamiento controla el acceso al estado interno; la herencia permite construir tipos a partir de otros y reutilizar o especializar comportamiento; y el polimorfismo permite utilizar una interfaz o tipo común con distintas implementaciones. En conjunto ayudan a modelar sistemas, aunque su uso debe responder a las necesidades reales del diseño.
Arquitectura de microservicios
38. ¿Qué es la arquitectura de microservicios?
Respuesta: Es un estilo arquitectónico en el que una aplicación se divide en servicios pequeños o acotados por capacidades de negocio, que pueden desarrollarse, desplegarse y escalarse de manera relativamente independiente. Los servicios se comunican mediante mecanismos como HTTP, mensajería o eventos y suelen ser responsables de sus propios datos o modelos de persistencia. La independencia no es absoluta: siguen existiendo contratos, dependencias de red y necesidades de coordinación entre servicios.
39. ¿Cuáles son sus ventajas y desventajas frente a una arquitectura monolítica?
Respuesta: Los microservicios pueden permitir despliegues y escalado independientes, autonomía de equipos y aislamiento de ciertos fallos, además de facilitar que diferentes servicios utilicen tecnologías adecuadas a sus necesidades. A cambio, introducen complejidad distribuida: comunicación por red, latencia, observabilidad, seguridad entre servicios, consistencia de datos, gestión de versiones y operación de infraestructura. Un monolito suele ser más sencillo de desarrollar, probar y desplegar al inicio; por eso, microservicios no son automáticamente mejores y deben justificarse por la escala organizacional o técnica del sistema.
Métodos HTTP en una API REST
40. ¿Cómo funcionan los métodos GET, POST, PUT, PATCH y DELETE?
Respuesta: GET solicita la representación de un recurso y debe ser seguro, es decir, no debe provocar cambios de estado intencionados en el servidor. POST envía datos para que el servidor procese una operación, como crear un recurso o ejecutar una acción, y no es idempotente por definición general. PUT crea o reemplaza la representación de un recurso en una URI determinada y es idempotente en su semántica; PATCH aplica una modificación parcial, cuya idempotencia depende de la operación definida; DELETE solicita eliminar el recurso y es idempotente en cuanto al efecto final esperado, aunque las respuestas pueden variar entre llamadas.
41. ¿Cuál es la diferencia entre GET, POST, PUT, PATCH y DELETE en un caso práctico?
Respuesta: En una API de clientes, GET /clientes/25 consultaría los datos del cliente 25; POST /clientes enviaría la información para crear un cliente nuevo; PUT /clientes/25 reemplazaría la representación completa del cliente 25; PATCH /clientes/25 cambiaría solo algunos campos, como su teléfono; y DELETE /clientes/25 solicitaría eliminarlo. La diferencia práctica está en la intención de cada operación y en si se consulta, crea, reemplaza, modifica parcialmente o elimina un recurso; el contrato exacto también debe documentar validaciones y códigos de respuesta.
Spring: anotaciones y patrones de diseño
42. ¿Qué son las anotaciones en Java y por qué se utiliza el símbolo arroba (@)?
Respuesta: Las anotaciones son metadatos que se agregan a clases, métodos, campos, parámetros u otros elementos para comunicar información a herramientas, al compilador o a frameworks. El símbolo @ identifica la sintaxis de una anotación, como @Override o @Deprecated, y permite que herramientas como Spring detecten configuraciones y comportamientos sin depender exclusivamente de archivos de configuración externos. Una anotación por sí sola no ejecuta necesariamente una acción: su efecto depende de quién la procesa y de cómo está definida.
43. ¿Cuál es la diferencia entre @Controller y @RestController?
Respuesta: @Controller identifica una clase que actúa como controlador dentro del modelo web de Spring MVC y suele utilizarse cuando los métodos devuelven nombres de vistas que serán renderizadas. @RestController combina el comportamiento de @Controller con @ResponseBody, por lo que los valores devueltos por sus métodos se escriben directamente en el cuerpo de la respuesta HTTP, normalmente serializados como JSON. Para APIs REST que entregan datos en lugar de vistas de servidor, generalmente se utiliza @RestController.
44. ¿Cuál es la diferencia entre @Component, @Service y @Repository?
Respuesta: Las tres anotaciones permiten que Spring detecte clases como beans administrados por el contenedor, pero expresan roles distintos. @Component es la marca genérica; @Service señala que la clase contiene lógica de servicio o de aplicación; y @Repository identifica una clase de acceso a datos y habilita, entre otras funciones, la traducción de ciertas excepciones de persistencia a la jerarquía de excepciones de Spring. La diferencia no es que cada una tenga un ciclo de vida completamente distinto, sino que comunican intención y pueden activar comportamientos específicos del framework.
45. ¿Cuáles son los principales patrones de diseño utilizados en Spring Boot con Java?
Respuesta: Entre los patrones habituales están Inyección de Dependencias e Inversión de Control, MVC, Repository, Service Layer, DTO, Factory, Builder, Proxy, Strategy, Adapter y Observer o publicación de eventos. Algunos son patrones generales de diseño y otros describen estructuras arquitectónicas o prácticas comunes en aplicaciones empresariales. Spring facilita varios de ellos mediante su contenedor de beans, Spring MVC, AOP, mecanismos de configuración y abstracciones de persistencia.
46. ¿Qué es cada uno de esos patrones y en qué casos se utilizan?
Respuesta: Inyección de Dependencias entrega a una clase los colaboradores que necesita, reduciendo el acoplamiento; MVC separa el manejo de solicitudes, el modelo y la presentación; Repository abstrae el acceso a datos; Service Layer concentra casos de uso; y DTO transporta información entre límites sin exponer necesariamente las entidades internas. Factory centraliza la creación de objetos, Builder facilita construir objetos complejos, Proxy controla el acceso o añade comportamiento como transacciones, Strategy permite intercambiar algoritmos, Adapter conecta interfaces incompatibles y Observer o los eventos notifican cambios a suscriptores. Se elige cada patrón cuando resuelve un problema concreto, no por obligación ni para aumentar artificialmente la cantidad de clases.
ORM
47. ¿Qué es un ORM y por qué utilizar uno?
Respuesta: Un ORM, o mapeador objeto-relacional, es una herramienta que relaciona objetos de un lenguaje de programación con tablas y relaciones de una base de datos relacional. En Java, JPA define una especificación de persistencia y Hibernate es una de sus implementaciones más conocidas; permiten trabajar con entidades, relaciones y consultas sin escribir manualmente todo el SQL. Un ORM puede acelerar el desarrollo y reducir código repetitivo, pero no elimina la necesidad de entender SQL, índices, transacciones y rendimiento, y debe utilizarse con atención a problemas como las consultas N+1.
Procesos Batch
48. ¿Qué es un proceso Batch?
Respuesta: Un proceso Batch es un trabajo que procesa un conjunto de datos o tareas en forma agrupada, normalmente sin interacción directa e inmediata con cada usuario. Puede ejecutarse de manera programada, por demanda o como parte de un flujo operativo, y suele leer datos, transformarlos, aplicar reglas y guardar resultados. Es útil para operaciones masivas, repetitivas o programadas que no necesitan responder en el mismo instante en que un usuario realiza una solicitud.
49. ¿Cuáles son algunos casos reales de uso de procesos Batch?
Respuesta: Algunos casos reales son el cálculo nocturno de intereses bancarios, la generación de nóminas, la conciliación de movimientos, la emisión masiva de facturas, la actualización de inventarios, la importación de archivos de proveedores y la generación de reportes periódicos. También se utilizan en procesos ETL para extraer, transformar y cargar grandes cantidades de información entre sistemas. La característica común es que existe un volumen o una secuencia de trabajo que conviene procesar de forma controlada y agrupada.
50. ¿Qué significa exactamente el concepto de procesamiento por lotes?
Respuesta: Significa reunir un conjunto de registros o tareas y procesarlos como una unidad lógica de trabajo, en lugar de tratar cada elemento como una solicitud interactiva independiente. El programa puede leer miles de registros, dividirlos en bloques, aplicarles reglas y persistir los resultados, controlando errores, reintentos y transacciones. Un lote no tiene que ser enorme: lo importante es el patrón de procesamiento agrupado y la forma de gestionar el trabajo.
51. ¿Podrías darme otro ejemplo de un proceso Batch?
Respuesta: Una tienda en línea podría ejecutar cada madrugada un proceso que lea los pedidos completados durante el día, verifique sus datos, calcule comisiones, actualice estadísticas de ventas y genere un archivo para el sistema contable. El trabajo se realizaría de manera programada y podría procesar los pedidos en bloques para controlar el uso de memoria y las transacciones. Si un registro falla, el proceso podría registrar el error y continuar o detenerse según las reglas operativas definidas.
52. Si tengo una aplicación sencilla con unos 100 usuarios, ¿sería innecesario implementar un proceso Batch porque son pocos usuarios?
Respuesta: No se decide por la cantidad de usuarios, sino por la naturaleza y el volumen del trabajo. Una aplicación con 100 usuarios podría necesitar un Batch para generar nóminas, cerrar operaciones del día, importar archivos o recalcular información de forma programada; otra con miles de usuarios quizá no lo necesite si todas sus operaciones son pequeñas y en tiempo real. La decisión debe considerar frecuencia, volumen de registros, duración, ventanas de ejecución, tolerancia a fallos y necesidad de auditoría.
Concurrencia: hilos en Java
53. ¿Qué son los hilos en Java y cuáles son sus casos de uso?
Respuesta: Un hilo es una unidad de ejecución dentro de un proceso; varios hilos pueden avanzar de forma concurrente y compartir la memoria del mismo proceso. En Java se utilizan para atender tareas concurrentes, realizar operaciones de entrada y salida sin bloquear otros trabajos, ejecutar tareas en segundo plano o aprovechar varios núcleos cuando las tareas son paralelizables. Como comparten recursos, también requieren gestionar correctamente la sincronización, la visibilidad de datos y las condiciones de carrera; en aplicaciones modernas suele ser preferible administrar tareas mediante executors o mecanismos de concurrencia de alto nivel en lugar de crear hilos sin control.
54. ¿Cuál es el caso más típico de utilización de hilos?
Respuesta: Un caso muy habitual es que un servidor web atienda varias solicitudes de clientes de manera concurrente mediante un conjunto administrado de hilos. Mientras una solicitud espera una consulta a la base de datos o una respuesta de otro servicio, el servidor puede avanzar en otras solicitudes, sujeto a los límites de sus recursos y de la configuración. También es frecuente utilizar hilos o ejecutores para tareas en segundo plano, procesamiento paralelo y operaciones de entrada y salida.
55. ¿La ejecución de varias tareas al mismo tiempo no debería resolverse automáticamente por el sistema?
Respuesta: El sistema operativo y la JVM se encargan de planificar y ejecutar los hilos disponibles, pero no deciden por sí solos qué partes de la lógica de la aplicación deben ejecutarse en paralelo ni cómo coordinar sus resultados. El desarrollador o el framework define las tareas, sus dependencias, los límites de concurrencia y las reglas para compartir datos; después, el entorno de ejecución las programa en los recursos disponibles. Por eso la concurrencia requiere diseño, aunque no siempre sea necesario administrar los hilos manualmente, ya que los servidores y frameworks suelen ofrecer mecanismos preparados.
56. ¿Cuál es la diferencia entre un proceso Batch y un hilo?
Respuesta: Batch describe una estrategia de procesamiento agrupado de datos o tareas, mientras que un hilo es una unidad de ejecución dentro de un proceso. Un Batch puede ejecutarse en un solo hilo o utilizar varios hilos para procesar partes del lote en paralelo; del mismo modo, los hilos pueden utilizarse para atender solicitudes en tiempo real sin que exista un proceso Batch. Por tanto, no son conceptos opuestos: uno describe qué tipo de trabajo se realiza y el otro cómo se ejecuta.
Principios SOLID
57. ¿Qué es SOLID y cuáles son sus principios?
Respuesta: SOLID es un conjunto de cinco principios de diseño orientado a objetos que busca facilitar el mantenimiento, la extensión y la comprensión del software. S corresponde a Responsabilidad Única: una clase debe tener una razón principal para cambiar; O, Abierto/Cerrado: debe poder extenderse sin modificar constantemente el código estable; L, Sustitución de Liskov: una implementación debe poder sustituir a su tipo base sin romper las expectativas; I, Segregación de Interfaces: es mejor ofrecer interfaces pequeñas y específicas; y D, Inversión de Dependencias: los módulos de alto nivel deben depender de abstracciones, no de detalles concretos.
58. ¿Cuál sería un caso de uso de SOLID?
Respuesta: En un sistema de pagos, en lugar de crear una sola clase que valide, procese, registre y notifique todos los pagos, separaría esas responsabilidades en componentes con funciones claras. El flujo principal dependería de una abstracción de medio de pago, y cada implementación (por ejemplo, tarjeta o transferencia) cumpliría el mismo contrato; así se podría añadir un nuevo medio sin reescribir la lógica central. Este diseño aplica responsabilidad única, inversión de dependencias, segregación de interfaces y sustitución de Liskov cuando las implementaciones respetan el contrato.
59. Si la abstracción de la programación orientada a objetos ya conlleva buenas prácticas, ¿por qué necesitamos agregar otra capa de principios como SOLID?
Respuesta: La programación orientada a objetos ofrece mecanismos como clases, interfaces, encapsulamiento y herencia, pero no garantiza que se utilicen correctamente ni que el resultado sea fácil de mantener. Es posible construir una jerarquía de clases muy acoplada o una clase enorme que concentre demasiadas responsabilidades usando únicamente herramientas de POO. SOLID aporta criterios para tomar decisiones de diseño y evaluar cómo organizar responsabilidades, extensiones y dependencias; complementa la POO, no la sustituye.
Arquitectura por capas
60. ¿Qué es la arquitectura de capas en Java y cómo funciona mediante la capa de presentación, la capa de aplicación, la capa de negocio y la capa de datos o DAO?
Respuesta: La arquitectura de capas separa una aplicación según las responsabilidades que cumple cada parte. La presentación recibe solicitudes y devuelve respuestas, por ejemplo mediante controladores REST; la capa de aplicación coordina los casos de uso y el flujo de la operación; la capa de negocio contiene las reglas y conceptos centrales del dominio; y la capa de datos o DAO se encarga de consultar y persistir información. En proyectos pequeños algunos equipos combinan la capa de aplicación y la de negocio, pero la separación conceptual ayuda a evitar que la interfaz o la persistencia controlen directamente las reglas del dominio.
61. ¿Por qué la capa de negocio suele estar separada de las demás capas?
Respuesta: Se separa para mantener las reglas del negocio independientes de detalles como el protocolo HTTP, la interfaz de usuario o el motor de base de datos. Así, una regla de cálculo o validación puede probarse sin levantar toda la aplicación y puede reutilizarse desde distintos puntos de entrada, como una API, un proceso Batch o una tarea interna. También facilita sustituir tecnologías externas sin tener que reescribir las decisiones centrales del negocio.
SQL y PL/SQL
62. ¿Cuáles son los principales comandos SQL y para qué sirven, como SELECT, INSERT, UPDATE, DELETE y JOIN?
Respuesta: SELECT consulta y devuelve datos; INSERT agrega registros; UPDATE modifica registros existentes; y DELETE elimina registros. JOIN combina filas relacionadas de dos o más tablas, por ejemplo, clientes con sus pedidos, utilizando una condición de unión; existen variantes como INNER JOIN, LEFT JOIN, RIGHT JOIN y FULL JOIN, según qué filas se quieran conservar. También son importantes los comandos de definición de estructura, como CREATE, ALTER y DROP, los de control de transacciones, como COMMIT y ROLLBACK, y los de permisos, como GRANT y REVOKE, según el motor de base de datos.
63. ¿Qué es PL/SQL y cuál es su diferencia con SQL?
Respuesta: PL/SQL es la extensión procedural de Oracle para SQL, que permite escribir bloques con variables, condiciones, ciclos, manejo de excepciones, procedimientos, funciones y triggers. SQL es un lenguaje declarativo utilizado principalmente para consultar y manipular datos y definir estructuras, indicando qué resultado se necesita. PL/SQL incorpora estructuras de programación para controlar secuencias de operaciones alrededor de sentencias SQL; es específico del ecosistema Oracle y no debe confundirse con las extensiones procedurales de otros motores, como T-SQL en SQL Server.
Angular: fundamentos, componentes y módulos
64. ¿Qué es Angular y cuáles son sus principales características?
Respuesta: Angular es un framework para desarrollar aplicaciones web, especialmente aplicaciones de una sola página, utilizando TypeScript y una arquitectura basada en componentes. Incluye herramientas integradas para plantillas, binding de datos, inyección de dependencias, enrutamiento, formularios, comunicación HTTP y pruebas, además de mecanismos reactivos como RxJS y, en las versiones modernas, Signals. Su enfoque integrado y estructurado ayuda a mantener aplicaciones grandes, aunque implica aprender las convenciones y herramientas propias del framework.
65. ¿Por qué los bancos suelen utilizar más Angular que React y combinarlo con Java?
Respuesta: No es correcto afirmar como regla general que los bancos utilizan más Angular que React: la elección varía según la institución, el país, el equipo y los sistemas existentes, y ambos se utilizan en aplicaciones empresariales. Angular puede resultar atractivo en organizaciones que valoran una estructura integrada, TypeScript, convenciones comunes, herramientas de formularios y una arquitectura consistente entre equipos; React también puede cumplir esos requisitos mediante su ecosistema. Java y Spring Boot son frecuentes en sistemas empresariales por su madurez, ecosistema, herramientas de seguridad e integración, pero no existe una obligación técnica de combinar Angular con Java ni una relación exclusiva entre ellos.
66. ¿Qué es un componente en Angular y cuáles son sus partes principales?
Respuesta: Un componente es una unidad de interfaz reutilizable que controla una parte de la pantalla y conecta la lógica de TypeScript con una plantilla visual. Sus partes principales son la clase que contiene el estado y los métodos, el decorador o metadatos @Component que configura el componente, la plantilla HTML que define la vista y los estilos asociados que determinan su presentación; también puede declarar dependencias e interactuar con otros componentes mediante entradas, salidas o servicios. En Angular actual pueden existir componentes standalone que gestionan sus dependencias sin necesitar que se declaren en un NgModule.
67. ¿Qué es un NgModule?
Respuesta: NgModule es un mecanismo de Angular que agrupa declaraciones de componentes, directivas y pipes, y define qué módulos o dependencias importa y qué elementos comparte con otros módulos. Históricamente fue la estructura principal para organizar aplicaciones y configurar partes del framework. En Angular moderno los componentes standalone permiten crear aplicaciones y funcionalidades sin depender obligatoriamente de NgModules, aunque estos siguen existiendo y se mantienen en muchos proyectos.
68. ¿Varios componentes pueden pertenecer a un mismo módulo?
Respuesta: Sí. Un NgModule puede declarar varios componentes, directivas y pipes relacionados, siempre que cumplan las reglas de declaración y pertenencia del framework; por ejemplo, un módulo de administración podría agrupar diferentes pantallas de esa funcionalidad. En el enfoque standalone, los componentes no se declaran dentro de un NgModule, sino que importan directamente las dependencias que necesitan, aunque una aplicación puede combinar ambos estilos durante una migración o por compatibilidad.
Angular: servicios, dependencias y Observables
69. ¿Qué es un servicio en Angular y qué es la inyección de dependencias?
Respuesta: Un servicio es una clase que encapsula una funcionalidad compartida, como consultar una API, administrar una sesión o coordinar una regla de aplicación, para que no tenga que repetirse dentro de los componentes. La inyección de dependencias es el mecanismo por el que Angular proporciona a una clase las instancias de los servicios u otras dependencias que necesita, según la configuración de proveedores y el ámbito correspondiente. De esta manera, el componente declara qué necesita sin tener que construir y administrar manualmente todos esos objetos.
70. ¿Podrías darme un ejemplo de un servicio y de una inyección de dependencias?
Respuesta: Imaginemos una aplicación que muestra el catálogo de productos: un servicio de catálogo sería responsable de consultar el backend y entregar los productos, mientras que el componente de la pantalla se encargaría de presentarlos. El componente declararía que necesita ese servicio y Angular se lo proporcionaría mediante su sistema de inyección de dependencias; así, la misma funcionalidad podría ser utilizada por varias pantallas sin duplicar la llamada HTTP. El servicio también podría sustituirse por una implementación simulada durante las pruebas.
71. ¿Por qué utilizar inyección de dependencias?
Respuesta: La inyección de dependencias reduce el acoplamiento porque una clase no necesita crear directamente todas las implementaciones concretas que utiliza. Facilita sustituir servicios, configurar diferentes ámbitos de vida, reutilizar dependencias y realizar pruebas unitarias con objetos simulados o alternativos. También centraliza la creación y administración de objetos, lo que hace más sencillo mantener aplicaciones con muchas dependencias.
72. ¿Qué es un Observable y cómo se utiliza RxJS?
Respuesta: Un Observable representa una secuencia de cero o más valores que puede emitirse a lo largo del tiempo y que puede terminar correctamente o producir un error. RxJS es una biblioteca que proporciona Observables y operadores para transformar, combinar, filtrar, controlar el tiempo y gestionar esas secuencias; para recibir sus valores normalmente se realiza una suscripción, aunque también pueden consumirse mediante mecanismos como el pipe async de Angular. Los Observables se utilizan en Angular para eventos, formularios, flujos de datos y, tradicionalmente, para las respuestas de HttpClient.
73. ¿Podrías darme un caso de uso de un Observable con RxJS?
Respuesta: Un caso común es un buscador que consulta productos mientras el usuario escribe. El flujo puede esperar brevemente después de cada tecla, ignorar textos repetidos y lanzar una nueva petición HTTP cuando el usuario deja de escribir durante ese intervalo; además, switchMap permite priorizar la búsqueda más reciente y dejar de escuchar la anterior si todavía no ha terminado. Este enfoque reduce solicitudes innecesarias y evita mostrar resultados antiguos después de que el usuario haya cambiado el criterio.
74. ¿Podría decirse que un Observable es parecido a consumir una API, pero con varios elementos suscritos y dispuestos a escuchar los datos que emite?
Respuesta: Es una comparación útil como primera aproximación, pero un Observable no es simplemente una API ni necesariamente tiene varios suscriptores. Es una representación de un flujo de valores en el tiempo: una petición HTTP puede emitir una respuesta y finalizar, mientras que un flujo de eventos puede emitir muchos valores durante un periodo largo. Los suscriptores reciben las emisiones; según el tipo de Observable, cada suscripción puede iniciar una ejecución independiente o conectarse a una fuente compartida.
Errores HTTP, Promise y Observable
75. ¿Cuáles son los diferentes tipos de errores HTTP, como 404 y 500, y qué significa cada uno?
Respuesta: Los códigos HTTP se agrupan por su primer dígito: 1xx indica información, 2xx éxito, 3xx redirección, 4xx errores asociados a la solicitud del cliente y 5xx errores del servidor al procesarla. Algunos ejemplos frecuentes son 400 solicitud incorrecta, 401 falta de autenticación válida, 403 acceso prohibido, 404 recurso no encontrado, 409 conflicto, 422 contenido no procesable, 429 demasiadas solicitudes, 500 error interno, 502 respuesta no válida de un servidor intermediario y 503 servicio no disponible. No todos los códigos 4xx o 5xx tienen el mismo significado, por lo que el cliente debe interpretar el código y, cuando exista, el cuerpo de error definido por la API.
76. ¿Cuál es la diferencia entre una Promise y un Observable?
Respuesta: Una Promise representa un único resultado futuro, ya sea resuelto o rechazado, mientras que un Observable puede emitir cero, uno o muchos valores a lo largo del tiempo y finalizar o fallar. Una Promise comienza su ejecución al ser creada y la API nativa no ofrece cancelación directa; un Observable de RxJS suele ser perezoso y empieza a producir valores al suscribirse, y cancelar la suscripción permite dejar de recibirlos y puede detener el trabajo subyacente si la fuente lo admite. Los Observables también ofrecen operadores para combinar y transformar flujos, mientras que las Promises se enfocan en un resultado asíncrono único.
Sass y lógica de negocio en componentes
77. ¿Por qué surgió Sass y qué relación tiene con CSS?
Respuesta: Sass surgió para añadir capacidades que facilitan organizar y reutilizar estilos, como variables, anidamiento, mixins, funciones y módulos, que durante mucho tiempo no estaban disponibles de forma equivalente en CSS. Es un preprocesador: se escribe en Sass o SCSS y luego se compila a CSS estándar que entiende el navegador. CSS moderno ya incorpora características como variables personalizadas y otras herramientas, pero Sass sigue siendo útil en proyectos que necesitan sus funciones y convenciones de organización.
78. ¿Por qué no es recomendable colocar toda la lógica de negocio dentro de un componente?
Respuesta: Porque un componente debería concentrarse principalmente en coordinar la interfaz y responder a las interacciones del usuario, no convertirse en el lugar donde vive toda la lógica de la aplicación. Si concentra validaciones complejas, reglas de negocio, llamadas HTTP y transformaciones de datos, se vuelve difícil de probar, reutilizar y mantener. Mover responsabilidades apropiadas a servicios o capas de aplicación permite que los componentes sean más pequeños y que las reglas puedan utilizarse desde distintas pantallas; aun así, no es necesario extraer cada operación trivial a otro servicio.
Carga diferida: Lazy Loading
79. ¿Qué es Lazy Loading?
Respuesta: Lazy Loading, o carga diferida, es una técnica que retrasa la carga de una parte de la aplicación hasta que realmente se necesita. En Angular se aplica con frecuencia a rutas o funcionalidades para que el usuario descargue inicialmente solo lo necesario y el resto se cargue cuando navega a esa sección. Puede reducir el tamaño del paquete inicial y mejorar el tiempo de arranque, aunque las funcionalidades diferidas deben descargarse cuando se solicitan y eso puede introducir una espera en la primera navegación a ellas.
80. ¿Lazy Loading pertenece principalmente al frontend o al backend?
Respuesta: En el contexto de Angular y de las aplicaciones web, suele hablarse de Lazy Loading principalmente en el frontend, donde se difiere la descarga o inicialización de módulos, rutas y componentes. Sin embargo, el concepto general también existe en el backend y en otros entornos, por ejemplo, cuando un ORM carga una relación de datos solo al acceder a ella o cuando una aplicación inicializa un recurso bajo demanda. Por lo tanto, no pertenece exclusivamente a una capa; su aplicación concreta depende de qué recurso se está cargando de manera diferida.
Angular, React, Vue y Lit
81. ¿Cuál es la principal diferencia entre Angular, React y Vue?
Respuesta: Angular es un framework amplio que ofrece una estructura integrada para construir aplicaciones, con soluciones oficiales para muchas necesidades comunes. React es una biblioteca centrada en construir interfaces mediante componentes y suele complementarse con herramientas externas para enrutamiento, estado y otras capacidades; Vue es un framework progresivo que proporciona una experiencia integrada y puede adoptarse gradualmente. Los tres permiten desarrollar interfaces basadas en componentes, pero difieren en su alcance, convenciones, ecosistema y grado de decisiones que dejan al equipo.
82. ¿Lit Element y Angular se parecen?
Respuesta: Se parecen en que ambos permiten construir interfaces mediante componentes, pero resuelven problemas a escalas diferentes. Lit, antes conocido en algunos contextos como LitElement, es una biblioteca ligera para crear Web Components basados en estándares del navegador, con elementos personalizados y encapsulamiento mediante Shadow DOM cuando corresponde; Angular es un framework integral para desarrollar aplicaciones, con enrutamiento, inyección de dependencias, formularios y otras herramientas. Un componente creado con Lit puede integrarse en una aplicación Angular, React o HTML estándar si se configura correctamente.
Router y directivas de Angular
83. ¿Cómo funciona el router en Angular?
Respuesta: Angular Router relaciona las direcciones URL con los componentes o vistas que deben mostrarse en la aplicación. Se configura un conjunto de rutas y se utiliza un espacio de salida, llamado router outlet, donde Angular representa el componente asociado a la ruta activa; también permite navegar mediante enlaces o código, utilizar parámetros, proteger rutas con guards y cargar funcionalidades de manera diferida. El router actualiza la vista y gestiona la navegación del lado del cliente sin que cada cambio de ruta tenga que provocar una recarga completa de la página.
84. ¿Cuáles son las principales directivas de Angular?
Respuesta: Las directivas clásicas integradas incluyen NgIf para mostrar o retirar elementos según una condición, NgFor para repetir una plantilla sobre una colección, NgSwitch para seleccionar entre alternativas, NgClass para aplicar clases dinámicamente y NgStyle para modificar estilos. NgModel también se utiliza en formularios basados en plantillas para enlazar datos. En Angular moderno existe además la sintaxis integrada de control de flujo, como @if, @for y @switch, que no son directivas tradicionales; también es posible crear directivas propias para añadir comportamiento o modificar la presentación de elementos.
TypeScript
85. ¿Qué es TypeScript y para qué sirve?
Respuesta: TypeScript es un lenguaje desarrollado sobre JavaScript que agrega un sistema de tipos estático y otras herramientas para describir mejor la estructura de los datos y las interfaces de una aplicación. Se utiliza para detectar incompatibilidades durante el desarrollo, mejorar el autocompletado, facilitar refactorizaciones y hacer más claros los contratos entre componentes y servicios. Normalmente el código TypeScript se transpila a JavaScript, que es lo que ejecutan los navegadores y los entornos habituales de JavaScript.
86. ¿Cuáles son sus principales diferencias con JavaScript?
Respuesta: JavaScript es el lenguaje que se ejecuta en el navegador y en entornos como Node.js, y utiliza tipado dinámico en tiempo de ejecución. TypeScript amplía JavaScript con tipos estáticos, interfaces, genéricos, anotaciones y comprobaciones que se realizan durante el desarrollo o la compilación; su sistema de tipos se elimina al generar el JavaScript final. TypeScript no reemplaza el entorno de ejecución de JavaScript: el programa resultante sigue ejecutándose como JavaScript y puede presentar errores de ejecución si la lógica o los datos reales no corresponden a las suposiciones del código.
Micro Frontends y Atomic Design
87. ¿Qué son los Micro Frontends?
Respuesta: Los Micro Frontends son un enfoque arquitectónico que divide una aplicación frontend grande en partes más pequeñas, alineadas con capacidades de negocio o equipos, que pueden desarrollarse y, en ciertos diseños, desplegarse de forma independiente. Una aplicación contenedora o mecanismo de integración reúne esas partes para que el usuario perciba una experiencia coherente. Pueden aportar autonomía y escalabilidad organizacional, pero también añaden complejidad en navegación, diseño compartido, autenticación, rendimiento, comunicación y consistencia visual.
88. ¿Qué es Atomic Design?
Respuesta: Atomic Design es una metodología para organizar y diseñar interfaces mediante componentes de distintos niveles de composición. Propone construir elementos básicos y reutilizables y combinarlos progresivamente en estructuras más completas, con el objetivo de mantener consistencia visual y facilitar la evolución de un sistema de diseño. No es un framework ni una arquitectura de despliegue, sino una forma de pensar y organizar la interfaz.
89. ¿Cómo funciona la división de componentes mediante átomos, moléculas, organismos, plantillas y páginas?
Respuesta: Los átomos son elementos básicos de interfaz, como botones, etiquetas, iconos o campos de entrada; las moléculas combinan varios átomos para cumplir una función pequeña, como un campo de búsqueda con etiqueta y botón. Los organismos agrupan moléculas y átomos para formar secciones completas, como una cabecera o un panel de filtros; las plantillas definen la estructura y distribución de una página, normalmente con contenido de ejemplo o espacios previstos; y las páginas son instancias concretas de esas plantillas con datos reales. La clasificación es una guía de diseño, no una regla rígida: debe ayudar a la reutilización y no generar componentes artificialmente pequeños.
90. ¿Cuál es la diferencia entre Micro Frontends y Atomic Design?
Respuesta: Micro Frontends es una estrategia de arquitectura y organización que divide una aplicación frontend en unidades funcionales que pueden pertenecer a equipos distintos y, según la solución, desplegarse independientemente. Atomic Design es una metodología para descomponer y componer la interfaz en componentes reutilizables, desde elementos básicos hasta páginas. Se pueden utilizar juntos: cada Micro Frontend puede organizar sus componentes con Atomic Design, porque uno aborda la división de la aplicación y el otro la composición de la interfaz.
Aplicación Full Stack con Angular, Java y SQL
91. ¿Cómo funciona una aplicación Full Stack que utiliza Angular, Java con Spring Boot y SQL?
Respuesta: Angular construye la interfaz que utiliza el usuario y envía solicitudes HTTP a una API; Spring Boot recibe esas solicitudes mediante controladores, valida y coordina los casos de uso mediante servicios y accede a la base de datos mediante repositorios u otras herramientas de persistencia. La base de datos SQL almacena y consulta la información, y el backend devuelve respuestas (frecuentemente JSON) que Angular utiliza para actualizar la pantalla. En una aplicación bien organizada, cada capa mantiene responsabilidades claras y el frontend no se conecta directamente a la base de datos.
92. ¿Cómo se comunican el frontend, el backend y la base de datos dentro de una aplicación Full Stack?
Respuesta: El usuario interactúa con Angular, que normalmente utiliza un servicio basado en HttpClient para enviar una petición HTTP a un endpoint del backend. Spring Boot recibe la solicitud en un controlador, aplica controles de autenticación y validación, delega la operación a la capa de aplicación o negocio y esta consulta o modifica la base de datos mediante la capa de persistencia; después, el backend convierte el resultado en una respuesta HTTP, normalmente con JSON y un código de estado. Angular interpreta la respuesta y actualiza la interfaz, mientras que los detalles internos de la base de datos permanecen protegidos detrás del backend.
Kubernetes, Git y GitHub Actions
93. ¿Para qué sirve Kubernetes?
Respuesta: Kubernetes es una plataforma de orquestación de contenedores que permite desplegar, programar y administrar aplicaciones contenerizadas en un conjunto de máquinas. Ayuda a mantener el número deseado de instancias, distribuir tráfico mediante servicios, reiniciar cargas que fallan, realizar actualizaciones controladas y gestionar configuración y secretos. Kubernetes no sustituye la construcción de imágenes ni corrige automáticamente los errores de una aplicación; coordina la ejecución y operación de las cargas de trabajo.
94. ¿Qué es GitHub?
Respuesta: GitHub es una plataforma para alojar repositorios Git y colaborar en proyectos de software. Git es el sistema de control de versiones que registra cambios en el código; GitHub añade herramientas web para gestionar repositorios, ramas, pull requests, revisiones, incidencias, permisos, documentación y automatizaciones. Por eso, Git y GitHub están relacionados, pero no son lo mismo: Git puede utilizarse sin GitHub y GitHub puede alojar proyectos gestionados con Git.
95. ¿Qué es GitHub Actions?
Respuesta: GitHub Actions es una plataforma de automatización integrada en GitHub que permite ejecutar flujos de trabajo cuando ocurre un evento, como un push, la creación de un pull request o una ejecución programada. Los workflows se definen mediante archivos de configuración y se componen de jobs y steps que se ejecutan en runners, pudiendo utilizar acciones reutilizables. Se emplea comúnmente para integración continua y entrega o despliegue continuo, aunque también puede automatizar tareas de mantenimiento y publicación.
96. ¿Cómo se utilizan GitHub Actions para automatizar pruebas, compilaciones y despliegues?
Respuesta: Se configura un workflow para que se active, por ejemplo, cuando alguien sube cambios o abre un pull request. El flujo obtiene el repositorio, prepara las versiones necesarias de Java o Node, instala dependencias, ejecuta análisis y pruebas, compila el backend y el frontend y conserva los artefactos resultantes; si las verificaciones pasan y se cumplen las condiciones de publicación, otro job puede construir una imagen de contenedor y desplegarla en el entorno correspondiente. Las credenciales deben manejarse mediante secretos y permisos mínimos, y los despliegues a producción suelen protegerse con aprobaciones o reglas de entorno.
Testing
97. ¿Qué son los tests o pruebas de software?
Respuesta: Son actividades diseñadas para comprobar que una parte del software se comporta como se espera y para detectar defectos antes de que afecten a los usuarios. Una prueba define un escenario, ejecuta el sistema o una parte de él y compara el resultado observado con el resultado esperado. Además de encontrar errores, las pruebas documentan comportamientos importantes y ayudan a evitar regresiones cuando el código cambia.
98. ¿Cuáles son los principales tipos de pruebas?
Respuesta: Las pruebas unitarias verifican unidades pequeñas de lógica de forma aislada; las pruebas de integración comprueban que varios componentes colaboren correctamente, por ejemplo, un servicio con una base de datos; y las pruebas end-to-end validan flujos completos desde la perspectiva del usuario o de un sistema consumidor. También existen pruebas de contrato para verificar acuerdos entre servicios, pruebas funcionales y de aceptación para confirmar requisitos, y pruebas no funcionales como rendimiento, carga, estrés, seguridad y accesibilidad. Cada tipo responde a un riesgo diferente, por lo que una estrategia equilibrada combina varios niveles.
99. ¿Qué herramientas se utilizan para realizar testing en Java y Spring Boot?
Respuesta: JUnit 5 es una de las herramientas más utilizadas para estructurar y ejecutar pruebas; Mockito permite crear dobles de prueba y simular dependencias; y AssertJ ofrece aserciones legibles. Spring Boot Test aporta soporte para probar el contexto y los componentes de Spring, MockMvc permite probar controladores MVC sin levantar un servidor HTTP real y WebTestClient puede utilizarse para probar aplicaciones web reactivas o endpoints HTTP; Testcontainers permite levantar dependencias reales, como bases de datos, dentro de contenedores para pruebas de integración. También son comunes REST Assured para APIs, JaCoCo para medir cobertura y Maven o Gradle para integrar las pruebas en la compilación.
100. ¿Las pruebas de software se realizan una vez que ya se ha finalizado todo el proyecto o durante el desarrollo?
Respuesta: Las pruebas deben realizarse durante todo el desarrollo, no únicamente al final. Lo habitual es escribir pruebas unitarias e integración conforme se implementan las funcionalidades, ejecutar verificaciones automáticamente en cada cambio mediante integración continua y realizar pruebas end-to-end y de aceptación en entornos preparados antes de liberar una versión. Al final también se ejecutan pruebas de regresión y validaciones completas, pero descubrir defectos desde etapas tempranas suele reducir el coste y el riesgo de corregirlos.
© Arturo Juárez Monroy