Fundamentos de Programación

En un mundo que ha sufrido un importante proceso de transformación digital cada vez hay más aplicativos desarrollados con el fin de interactuar con los seres humanos, si estos aplicativos no están correctamente programados un atacante puede hacer uso mal intencionado del software y controlar el flujo de ejecución, pudiendo alterar la información y/o robarla.

Antes de nada, haremos una distinción fundamental entre hardware y software

  • Hardware: Todos los componentes físicos de un ordenador.
  • Software: Programas e instrucciones para ejecutar tareas en un ordenador.

Una vez tenemos claro la diferencia, en esta entrada de este blog nos centraremos en el software, y en una de sus unidades fundamentales: Los Programas.

Definiríamos programa como:

  1. Secuencia de instrucciones.
  2. Entendible por el ordenador.
  3. Tienen un objetivo o tarea concreta.

Los ordenadores son máquinas eléctricas que sólo entienden de 0 y 1, siendo:

  • 0 no hay corriente (0.3 voltios).
  • 1 hay corriente (0.5 voltios).

Este es el único lenguaje que entiende el ordenador, llamado lenguaje binario.

Un programa es un conjunto de instrucciones que se crea para realizar una tarea específica en una computadora. Estas instrucciones son escritas en un lenguaje de programación y se traducen a un lenguaje que la computadora puede entender y ejecutar. Los programas pueden ser simples, como una calculadora, o muy complejos, como un sistema operativo. En esencia, un programa es como una receta que le dice a la computadora qué hacer y cómo hacerlo. Los programadores crean programas para automatizar tareas, procesar datos, resolver problemas y realizar una amplia variedad de funciones en la computadora.

Como ya hemos dicho para crear un programa y que la computadora lo interprete y ejecute, las instrucciones deben escribirse en un lenguaje de programación. En los primeros tiempos de la computación se programaba directamente en código máquina. Escribir programas así resultaba demasiado complicado, también era difícil entenderlos y mantenerlos una vez escritos. Con el tiempo, se fueron desarrollando herramientas para facilitar el trabajo y aparecieron los lenguajes de programación, que podemos dividir en dos grupos:

  • Lenguajes de bajo nivel: son los más cercanos al binario, pero son difíciles de programar.
  • Lenguajes de alto nivel: necesitan ser traducidos antes de llegar al ordenador, pero son fáciles de programar ya que son más cercanos al lenguaje natural de las personas

Algunos ejemplos:

  • Lenguajes bajo nivel: lenguaje máquina y Ensamblador.
  • Lenguajes alto nivel: C, C++, Java, PHP, Python, Go, Rust, Ruby...

En el desarrollo de software hay muchas condicionantes que marcan que elección de lenguaje se usará en el proyecto:

  • Requisitos técnicos marcados por otras piezas de software/hardware.
  • Tiempo de entrega del software.
  • Necesidad de rendimiento.

Programar viene a ser el proceso de crear un software fiable mediante la escritura en un lenguaje (code en inglés), prueba (test), depuración (debug), compilación o interpretación, y mantenimiento (maintenance) del código fuente de dicho programa informático.

CODIFICAR ⇒ PROBAR ⇒ DEPURAR ⇒ COMPILAR/INTERPRETAR ⇒ MANTENER (y vuelta a empezar)

El funcionamiento de un equipo informático es relativamente sencillo. En un área de la memoria principal se tiene un conjunto de bits agrupados en unidades denominadas instrucciones, que son potencias de 2 y cuyo tamaño depende de la arquitectura del equipo: 8 bits, 16 bits, 32 bits, 64 bits, 128 bits. El procesador obtiene la siguiente instrucción a ejecutar, la interpreta y manda "órdenes" a los diferentes elementos del equipo como registros, memoria principal, ALU o periféricos, una vez finalizada la instrucción pasa a la siguiente instrucción de la memoria principal.


Lenguajes más Usados en la Actualidad
A enero de 2025, los lenguajes de programación más utilizados reflejan las tendencias del desarrollo web, la inteligencia artificial, el análisis de datos y los sistemas empresariales:
  • JavaScript: es el lenguaje más demandado actualmente. Desde su origen como herramienta para crear sitios web dinámicos, ha evolucionado hacia un ecosistema completo para el desarrollo frontend y backend. Entornos como Node.js y herramientas como Electron han extendido su uso a aplicaciones móviles y de escritorio. Con la aparición de TypeScript, que añade tipado estático a JavaScript, se ha reforzado su uso en grandes proyectos empresariales y frameworks como Angular o NestJS. Su presencia en el mercado laboral supera el 31% de las ofertas. Además, su formato de intercambio de datos, JSON, se ha consolidado como estándar en APIs y comunicación entre servicios.
  • Python: destaca por su sintaxis clara y su enorme versatilidad. Se utiliza intensamente en campos como inteligencia artificial, ciencia de datos, desarrollo web, automatización de tareas y scripting. Librerías como TensorFlow, Keras, Pandas o NumPy han posicionado a Python como el lenguaje de referencia en proyectos de análisis de datos y machine learning. Su uso en desarrollo web también es notable gracias a frameworks como Django y Flask, ideales para crear aplicaciones escalables. Python representa aproximadamente el 20% de las ofertas laborales y es uno de los lenguajes con mayor crecimiento en sectores como el financiero, el académico o el tecnológico.
  • Java: mantiene su lugar como lenguaje de referencia en entornos corporativos. Su robustez, portabilidad y madurez lo hacen ideal para desarrollar aplicaciones empresariales, sistemas distribuidos y software Android. Frameworks como Spring lo mantienen en el centro del desarrollo backend a gran escala. Java sigue siendo fundamental en sectores críticos como la banca, seguros, telecomunicaciones y servicios públicos. A pesar del ascenso de alternativas como Kotlin, Java ocupa el 18% de las ofertas de empleo, lo que lo consolida como uno de los lenguajes más estables del mercado.
  • C#: desarrollado por Microsoft, se emplea ampliamente en el entorno .NET para crear aplicaciones de escritorio, web y móviles. Es especialmente fuerte en el mundo de los videojuegos gracias a Unity, y en el desarrollo multiplataforma mediante Xamarin. En la actualidad, C# también ha ganado terreno en desarrollo web moderno con tecnologías como Blazor, que permite crear interfaces interactivas con C# en el navegador. Su flexibilidad y rendimiento lo convierten en una herramienta muy utilizada en empresas tecnológicas y estudios de videojuegos. C# representa alrededor del 12% de las ofertas laborales.
  • PHP: sigue siendo uno de los lenguajes más utilizados en el desarrollo web del lado del servidor. Es la base de plataformas como WordPress, y se usa con frecuencia junto a frameworks como Laravel, que proporciona una arquitectura moderna y escalable. PHP es común en aplicaciones de comercio electrónico, como Magento o WooCommerce, y sigue siendo una opción accesible para desarrolladores por su baja barrera de entrada. A pesar de las críticas a su diseño, sigue representando un 10% del mercado laboral, especialmente en pequeñas y medianas empresas.
  • C y C++: son lenguajes esenciales para el desarrollo de software de bajo nivel, donde el control sobre la memoria y el hardware es crítico. C es ampliamente utilizado en sistemas embebidos y desarrollo de sistemas operativos, mientras que C++, con sus capacidades orientadas a objetos, se emplea en videojuegos (Unreal Engine), simulaciones científicas, sistemas bancarios y control de infraestructuras críticas. Aunque su demanda ha descendido ligeramente, C/C++ todavía representa un 6–7% del mercado, especialmente en entornos que requieren máxima eficiencia.
  • Ruby: es conocido por su enfoque en la simplicidad y la productividad, siendo especialmente popular en el desarrollo web gracias a Ruby on Rails. Este framework ha sido clave en el crecimiento de muchas startups por su rapidez para lanzar productos al mercado. Empresas como GitHub, Shopify o Basecamp han apostado por Ruby en sus orígenes. Aunque su presencia es menor que la de otros lenguajes, sigue representando alrededor del 4% de las ofertas laborales, con una comunidad activa y leal.
  • Go (Golang): desarrollado por Google, ha ganado relevancia por su eficiencia y rendimiento en sistemas concurrentes. Es muy utilizado en servicios web, microservicios y plataformas en la nube, por su capacidad para gestionar múltiples procesos de forma eficiente. Go es empleado en empresas como Uber, Dropbox o Netflix. Su diseño simple y sus herramientas integradas lo hacen ideal para backend moderno. Aunque su cuota de mercado es más baja (2%), su adopción sigue creciendo en entornos de infraestructura y alto rendimiento.
  • Bash/Shell: no es un lenguaje de propósito general, pero es fundamental en sistemas Unix/Linux para la automatización de tareas administrativas, la gestión de servidores y la creación de scripts de despliegue. Es indispensable en entornos DevOps, administración de redes y CI/CD. Aunque no aparece entre los lenguajes principales en desarrollo de aplicaciones, su dominio es obligatorio para perfiles de infraestructura.
Otros lenguajes en uso:
  • Rust
  • Kotlin
  • Lua
  • Dart
  • Assembly
  • Swift
  • R
  • Go
  • Visual Basic (.Net)
  • MATLAB
  • Scratch
Representacion de Tipos de Datos
Un tipo de dato es un atributo que indica la naturaleza de un valor, informando al lenguaje y al programador sobre qué clase de datos se está manejando, qué valores puede tomar y qué operaciones se pueden realizar sobre ellos. Es un concepto esencial, ya que determina cómo se almacenan, interpretan y manipulan los datos dentro del programa.

Un tipo de dato puede entenderse como una porción de memoria con restricciones concretas. Por ejemplo, un int suele representar un número entero de 32 bits, con un rango aproximado de 2.147.483.648 a 2.147.483.647, y permite operaciones como suma o multiplicación. En contraste, un color puede representarse mediante una tripleta de bytes (RGB) o como una cadena de texto ("azul", "rojo"), dependiendo del lenguaje y del contexto.

La forma en que se gestionan los tipos de datos depende del lenguaje de programación. Algunos lenguajes, como Java, son fuertemente tipados y estáticos, lo que significa que cada variable debe tener un tipo definido desde el momento en que se declara. Esto proporciona una mayor seguridad en tiempo de compilación, ya que el compilador detecta errores de tipo antes de ejecutar el programa. Por ejemplo, no es posible sumar una cadena de texto con un número sin conversión explícita previa.

Por otro lado, lenguajes como Python son dinámicamente tipados, lo que significa que no es necesario declarar el tipo de una variable, y este puede cambiar en tiempo de ejecución. Esto ofrece mayor flexibilidad, pero también implica que ciertos errores de tipo solo se detecten al ejecutar el programa, no durante la fase de desarrollo.

Además de los tipos básicos (enteros, reales, booleanos, cadenas, etc.), la mayoría de lenguajes modernos permiten crear tipos de datos personalizados o compuestos, como estructuras, clases u objetos. Por ejemplo, se puede definir un tipo Persona con atributos como nombre, edad o fecha_de_nacimiento, e implementar operaciones específicas como calcularEdad(). Esta capacidad de modelar entidades complejas resulta fundamental en la programación orientada a objetos y en la construcción de sistemas realistas.

Tipos de Datos Primitivos
Se denominan tipos primitivos a los tipos de datos básicos proporcionados directamente por el lenguaje de programación. Estos sirven como bloques fundamentales para construir estructuras de datos más complejas o tipos personalizados. Entre los más comunes:
  • Carácter (char): Representa un único símbolo, como letras, dígitos o signos de puntuación. Internamente, cada carácter se codifica mediante estándares como ASCII (7 bits) o Unicode, este último necesario para soportar múltiples idiomas y conjuntos de símbolos ampliados. Además de caracteres visibles, también incluye caracteres de control como el salto de línea o el tabulador, usados para gestionar el formato del texto.
  • Entero (int): Almacena números enteros sin decimales. El rango de valores depende del número de bits utilizados: por ejemplo, un entero de 8 bits puede representar de 0 a 255 (sin signo) o de -128 a 127 (con signo). Los enteros son esenciales en cálculos discretos,estructuras indexadas (como arrays) y en programación de bajo nivel. Los lenguajes suelen diferenciar entre enteros con signo y sin signo, lo que afecta tanto al rango como al comportamiento de las operaciones.
  • Real o coma flotante (float, double): Representa números reales con decimales, útiles en cálculos matemáticos y científicos. Se basan en el estándar IEEE 754, y suelen diferenciarse en:
    • Precisión simple (32 bits): Ahorra memoria, con menor precisión decimal.
    • Precisión doble (64 bits): Ofrece mayor precisión, a costa de más uso de memoria. Son indispensables en simulaciones, visualización gráfica y procesamiento numérico, aunque deben manejarse con cuidado debido a problemas de redondeo o pérdida de precisión.
Otros tipos de datos que pueden ser considerados primitivos (aunque no todos los lenguajes los proporcionan):
  • Booleano (bool): Solo puede tomar dos valores: verdadero (true) o falso (false). Este tipo es fundamental en estructuras de control como if, while o for, y se basa en lógica binaria. Permite evaluar condiciones y tomar decisiones mediante operadores lógicos como AND (&&), OR (||) y NOT (!). Su uso está muy extendido en validaciones, algoritmos condicionales y estructuras de decisión.
  • Cadena de caracteres (string): Es una secuencia ordenada de caracteres, normalmente almacenada como un array de char. Las cadenas permiten operaciones como concatenación, búsqueda de patrones, extracción de subcadenas, y son claves en aplicaciones de entrada/salida de texto, bases de datos o comunicación en red. En la mayoría de lenguajes, los strings se manejan como objetos o tipos de datos especiales, con métodos específicos para su manipulación.
  • Puntero (pointer): Almacena una dirección de memoria, es decir, apunta al lugar donde se encuentra almacenado otro dato. El uso de punteros permite acceso directo y eficiente a estructuras dinámicas, como listas enlazadas o árboles binarios. También se emplean para invocar funciones, gestionar memoria dinámica o construir interfaces entre módulos. Sin embargo, su uso indebido puede provocar fallos graves de seguridad o errores de ejecución, como accesos inválidos o fugas de memoria, motivo por el cual muchos lenguajes modernos restringen o controlan su uso mediante técnicas de verificación de tipos o manejo seguro de referencias.
Estos tipos primitivos no solo permiten representar información básica, sino que también son la base operativa de todas las estructuras de datos, desde arreglos hasta objetos complejos. Comprender sus características, ventajas y limitaciones es esencial para desarrollar software eficiente, robusto y seguro.

Operadores
Las expresiones son combinaciones de constantes, variables y operadores que nos permiten manipular datos para obtener información. Estas expresiones se evalúan aplicando los operadores a los operandos según reglas específicas, como la precedencia y el agrupamiento de operadores, conceptos similares a los de las matemáticas tradicionales.

Una expresión está compuesta por operandos (constantes, variables u otras expresiones) y operadores (símbolos que indican la operación a realizar). Dependiendo de los operandos y del resultado que generan, los operadores pueden clasificarse en aritméticos, relacionales, lógicos y de conversión de tipos. Los operadores nos permiten realizar tareas desde cálculos básicos hasta operaciones más avanzadas, según el tipo de datos que se manipulen.

Nos centraremos en el uso y función de los operadores:
  • Aritméticos: Los operadores aritméticos son fundamentales en cualquier lenguaje de programación y se utilizan para realizar cálculos básicos entre los tipos de datos numéricos. Entre las operaciones más comunes se encuentran la suma, resta, multiplicación, división y módulo. Algunos lenguajes también permiten utilizar ciertos operadores aritméticos para trabajar con cadenas de caracteres, como la concatenación.
  • Unarios:  Los operadores unarios son aquellos que actúan sobre un único operando y realizan operaciones específicas como incremento, decremento o negación. Aunque no están presentes en todos los lenguajes de programación, son fundamentales en aquellos que los implementan, como C, C++, Java y JavaScript. A continuación, se muestran dos operadores unarios comunes:
  • Relacionales:  Los operadores relacionales son símbolos que se usan para comparar dos valores. Si el resultado de la comparación es correcto, la expresión se considera verdadera; en caso contrario, es falsa.
  • Lógicos: Los operadores lógicos son utilizados en lenguajes de programación para combinar expresiones. Los más comunes son:
    • AND (&&): El resultado es verdadero si ambas expresiones son verdaderas.
    • OR (||): El resultado es verdadero si alguna de las expresiones es verdadera.
    • NOT (!): El resultado invierte la condición de la expresión.
AND y OR trabajan con dos operandos y retornan un valor lógico basado en las denominadas tablas de verdad. El operador NOT actúa sobre un solo operando.

Otros Operadores Lógicos menos comunes
  • Operador NAND: En el operador NAND, cuando las dos entradas son altas, la salida será baja. Es la negación del operador AND.
  • Operador NOR: En el operador NOR, cuando ambas entradas están en estado bajo, la salida será alta. Es la negación del operador OR.
  • Operador XOR: En el operador XOR, la salida será baja cuando ambas entradas estén en el mismo estado (bajo o alto). La salida será alta cuando las entradas sean diferentes.
  • Operador XNOR: En el operador XNOR, la salida será baja cuando una de las dos entradas esté en estado alto. Es la negación del operador XOR.

Fundamentos de los lenguajes más utilizados I
Si bien en la actualidad existen múltiples técnicas, paradigmas y filosofías en los lenguajes de programación como programación orientadas a aspectos, dirigida por eventos o basada en funciones lambda entre muchas otras, los lenguajes más utilizados se basan en unos elementos y estructuras comunes.

Variables
Asociación de un nombre simbólico a un espacio de memoria, de forma que se puede referenciar por el nombre y no por la dirección, siendo posible consultar y modificar su valor. En los lenguajes de tipado fuerte es necesario indicar el tipo de dato como C o C++, en otros denominados no tipados no es necesario indicarlo como PHP o Javascript. También se ha de distinguir entre los lenguajes de tipado dinámico y estático que indican en qué momento se realiza la comprobación de tipos, en la ejecución o en la compilación.

Constantes
Al igual que las variables se asocia un nombre simbólico a un área de memoria, pero en este caso solo es posible la lectura, no la modificación.

Sentencias de control alternativas
Expresiones que permiten modificar el flujo de ejecución del programa dependiendo de ciertas condiciones. La sintaxis dependerá del lenguaje concreto utilizado. Se clasifican en alternativas o condicionales e iterativas o repetitivas. Las primeras modifican el flujo dependiendo de la evaluación de una condición lógica y se decide qué conjunto o bloque de instrucciones se ejecutarán a continuación. Las más usadas son:
  • If-else: Se indica una condición que al evaluarse devuelve cierto o falso y en función del resultado ejecuta uno u otro bloque.
                if (condición) then
                bloque instrucciones si cierto
                else
                bloque instrucciones si falso
                endif
  • Switch: Permite evaluar una expresión y ejecutar más de dos posibles opciones en función de la expresión. Dependiendo del lenguaje, la expresión se puede limitar a comparar números enteros o caracteres como en C o en lenguajes más recientes como C# con enteros, caracteres, cadenas, booleanos o enumerados.
                switch (expresión):
                case X:
                bloque instrucciones
                break
                case Y:
                bloque instrucciones
                break
                default:
                bloque instrucciones
                endswitch

Sentencias de control iterativas o repetitivas
Con este tipo de estructuras se puede repetir un bloque de código un número concreto de veces en función de la evaluación cierta de una condición lógica. Son también conocidas como bucles.
  • While: Se ejecuta el bloque contenido en la estructura mientras se cumpla la condición lógica. Se puede dar el caso de no ejecutarse ninguna vez.
                While (condición):
                bloque instrucciones.
                endwhile
  • Do-While: Se ejecuta el bloque contenido en la estructura mientras se cumpla la condición lógica. La evaluación de la condición se ejecuta al final del bloque de instrucciones.
                Do (condición):
                bloque instrucciones.
                While
  • For: En este caso, además de la condición, posee un bloque de instrucciones que se ejecutan al inicio, el bloque en el que se encuentra la condición a evaluar en cada iteración y por último un bloque de instrucciones que se ejecuta en cada iteración del bucle. Si bien es posible indicar varias instrucciones separadas por comas en cada elemento del bucle for, no suele ser usual utilizarlo ya que no es intuitivo...
                For (bloque inkialización; bloque condición; bloque iteración):
                bloque instrucciones,
                endFor

La evaluación de la condición en las estructuras While y for, se realiza al inicio. En el caso de la estructura do-while se evalúa la condición al final de la ejecución del bloque de instrucciones, por tanto se ejecuta al menos una vez.

Fundamentos de los lenguajes más utilizados II
Otro de los fundamentos de los lenguajes de programación son las funciones o procedimientos, aunque estos últimos no son muy utilizados en la actualidad. Se puede definir una función como fragmento de código identificado con un nombre, que puede ser reutilizados utilizando ese nombre, que devuelve un valor de un tipo de dato, que recibe una serie de argumentos de un determinado tipo llamados parámetros, en cada una de las llamadas. Aunque esta definición es de lenguajes fuertemente tipados, en los débilmente la definición es similar pero sin ser necesario tener en cuenta los tipos de datos utilizados.

Dependiendo del lenguaje la declaración, llamada a la función y devolución de valores puede variar en cuanto a la sintaxis, pero los fundamentos son los mismos. Un ejemplo de función en C:


Es necesario destacar que existen 2 formas de indicarle a la función que parámetros tiene que utilizar y de qué forma:
  • Por valor: Se realiza una copia de la variable y esta copia es la que recibe la función, por tanto los cambios realizados en función no tienen consecuencias en la variable original.
  • Por referencia: Ya sea con los mecanismos que provee de C (dirección de memoria del inicio de la variable que se pasa como argumento, conocida como puntero), Java (en las variable original se almacenan la dirección del objeto y se realiza una copia de la  variable a pasar corno argumento, referenciando la copia también al objeto) o de otros lenguajes, las consecuencia principal es que las modificaciones realizadas dentro de la función afectan a la variable pasada como argumento.
Programación Orientada a Objetos (POO)
Es un paradigma de programación que modela el software en términos de "objetos" que interactúan entre sí. Cada objeto representa una entidad del mundo real o lógico, y encapsula en su interior tanto datos (atributos) como comportamientos (métodos). Este enfoque permite construir aplicaciones más modulares, reutilizables y mantenibles, facilitando la escalabilidad de los proyectos.

En la POO, el foco no está en la secuencia de instrucciones (como en la programación estructurada), sino en la interacción entre objetos autónomos que colaboran para cumplir los objetivos del sistema. Este paradigma es ampliamente adoptado en lenguajes modernos como Java, C++, C#, Python, Ruby o PHP, y constituye la base de numerosos entornos de desarrollo y frameworks.

Entre sus principios fundamentales se encuentran:
  • Encapsulación: Cada objeto protege sus datos internos, exponiendo solo lo necesario a través de una interfaz definida. Esto favorece la seguridad y la modularidad del código, al evitar dependencias innecesarias entre componentes.
  • Abstracción: Los objetos modelan conceptos del sistema, ocultando la complejidad interna y mostrando solo lo relevante para el uso externo. Este principio permite centrarse en lo esencial y reducir el acoplamiento.
  • Herencia: Permite crear nuevas clases basadas en clases existentes, reutilizando código y favoreciendo la organización jerárquica de conceptos. Las subclases heredan atributos y métodos de sus superclases, pudiendo extender o modificar su comportamiento.
  • Polimorfismo: Los objetos pueden responder de manera diferente a una misma operación, dependiendo de su tipo o contexto. Esto se traduce en la posibilidad de usar interfaces comunes con implementaciones diversas, facilitando la extensibilidad del sistema.
La POO también introduce los conceptos de clase (la definición o plantilla de un objeto) y instancia (una realización concreta de esa clase en memoria). Una clase define qué atributos y métodos tendrá el objeto, mientras que una instancia representa un objeto funcional en el programa.

Gracias a estas características, la Programación Orientada a Objetos es especialmente adecuada para aplicaciones complejas, mantenibles y colaborativas, donde múltiples desarrolladores pueden trabajar sobre módulos independientes. Es también el paradigma más usado en la enseñanza de programación y en el desarrollo profesional de software.

En la práctica, la POO permite dividir un problema grande en piezas pequeñas (objetos), favoreciendo la reutilización de código, la evolución de los sistemas a largo plazo y la mejora continua de su diseño. Sin embargo, como cualquier paradigma, su uso inadecuado (por ejemplo, un diseño excesivamente fragmentado o una jerarquía de clases innecesariamente compleja) puede llevar a sistemas difíciles de entender y mantener. Por ello, dominar sus principios y aplicarlos con criterio resulta esencial para diseñar software robusto.

Librerías en Programación
En informática, una librería o biblioteca es un conjunto de implementaciones funcionales, codificadas en un lenguaje de programación, que ofrece una interfaz bien definida para invocar funcionalidades específicas. Estas funcionalidades suelen incluir soluciones reutilizables para problemas comunes, como el manejo de datos, operaciones matemáticas avanzadas, generación de gráficos o comunicación en red, lo que facilita y acelera el desarrollo de software.

A diferencia de un programa ejecutable, el comportamiento de una librería no está diseñado para ser utilizado de manera autónoma, sino como un recurso compartido que puede ser aprovechado por otros programas simultáneamente. Las librerías actúan como módulos auxiliares, permitiendo a los desarrolladores enfocarse en el diseño y la lógica de su aplicación sin necesidad de reinventar funcionalidades básicas.

Además, el comportamiento de una librería no tiene por qué diferir demasiado del que se implementaría en un programa, aunque su diseño busca ser genérico y flexible para adaptarse a diferentes casos de uso. Algunas bibliotecas dependen de otras para funcionar correctamente, formando un ecosistema en el que estas librerías pueden modificar, refinar o incluso extender el comportamiento de las originales. También pueden hacer estas funcionalidades disponibles para otras tecnologías o lenguajes de programación, ampliando su utilidad.

Las librerías pueden ser clasificadas en dos tipos principales:
  • Librerías estáticas: Se integran directamente en el archivo ejecutable del programa durante el proceso de compilación. Esto significa que, una vez compilado, el programa no necesita la librería por separado para funcionar, pero aumenta el tamaño del ejecutable.
  • Librerías dinámicas (o compartidas): Estas se cargan durante la ejecución del programa, permitiendo que varios programas utilicen la misma librería sin necesidad de duplicarla en cada uno. Ejemplos comunes incluyen DLLs (Dynamic Link Libraries) en Windows o .so (Shared Objects) en sistemas Unix/Linux.
El uso de librerías ofrece múltiples ventajas, como:
  • Reutilización del código: Evita la duplicación de esfuerzos y mejora la eficiencia del desarrollo.
  • Facilidad de mantenimiento: Las actualizaciones o correcciones en una librería pueden beneficiar a todos los programas que la utilizan.
  • Reducción de errores: Al utilizar código probado y validado, se minimizan los riesgos de fallos.
  • Abstracción: Permite al desarrollador utilizar funcionalidades complejas sin necesidad de entender los detalles internos de su implementación
En la actualidad, las librerías son una parte fundamental del desarrollo de software y se encuentran en todos los niveles del proceso de creación, desde herramientas de bajo nivel como libc (librería estándar de C) hasta frameworks y paquetes más avanzados como TensorFlow para machine learning o React para desarrollo web.

Modelos de Ejecución Software
Podemos clasificar los lenguajes de programación, según su modo de ejecución, en 3 tipos distintos:
  • Lenguaje compilado
    • Nuestro código fuente se transforma en un binario mediante compilación.
    • El ordenador ejecuta el binario.
    • No necesitamos por tanto ningún programa adicional.
  • Lenguaje interpretado
    • Nuestro código fuente es leído en tiempo real por un programa (intérprete) que lo traduce a código máquina (objeto binario).
    • El ordenador ejecuta ese binario.
    • Necesita por tanto un programa adicional, llamado intérprete, que hace de traductor entre las instrucciones y el código máquina.
  • Lenguaje intermedio
    • El código fuente se compila a un lenguaje intermedio.
    • Este lenguaje intermedio se ejecuta en una máquina virtual.
Lenguajes de Programación Interpretados y Compilados
Los traductores son programas cuya finalidad es convertir lenguajes de alto nivel (en los que se programa) a lenguajes de bajo nivel como ensamblador o código máquina. Existen dos grandes grupos de tipos de traductores: los compiladores y los intérpretes:
  • Un intérprete traduce el código fuente linea a línea como se describe a continuación: primero traduce la primera línea, detiene la traducción y, seguidamente la ejecuta; lee la siguiente línea, detiene la traducción y la ejecuta, y así sucesivamente. El intérprete tiene que estar cargado en memoria ejecutándose para poder ejecutar el programa. Al igual que el intérprete, el código fuente tiene que estar también en la memoria. En caso de detectar un error durante el proceso de traducción el intérprete detiene la ejecución del programa. 
    • PHP.
    • Perl.
    • Python.
    • Ruby.
  • Un compilador traduce un programa entero de un lenguaje de programación (llamado código fuente) a otro denominado lenguaje objetivo. Usualmente, el lenguaje objetivo es código máquina, aunque también puede ser traducido a un código intermedio (bytecode) o a texto. El compilador únicamente estará instalado en la máquina de desarrollo. El código generado para un sistema sólo funcionará para una arquitectura hardware y software determinadas. Si se desea ejecutar en un sistema con un hardware o software diferente habrá que volver a recompilarlo. Ejemplos de lenguajes compilados típicos son:
    • C/C++.
    • Pascal.
    • Entre otros.

Si observamos las diferencias entre compilador e intérprete, vemos claramente los puntos fuertes y débiles de cada solución para traducir el código fuente; con el intérprete, los programas se pueden ejecutar de inmediato y, por lo tanto, se inician mucho más rápido. Además, el desarrollo es mucho más fácil que con un compilador, porque el proceso de depuración (es decir, la corrección de errores) se lleva a cabo igual que la traducción, línea por línea. En el caso del compilador, primero debe traducirse todo el código antes de poder resolver los errores o iniciar la aplicación. Sin embargo, una vez que se ejecuta el programa, los servicios del compilador ya no son necesarios, mientras que el intérprete continúa utilizando los recursos informáticos.

Algunas ventajas de compilar frente a interpretar son:
  • Se compila una vez; se ejecuta muchas veces.
  • La ejecución del programa objeto es mucho más rápida que si se interpreta el programa fuente.
  • El compilador tiene una visión global del programa, por lo que la información de los mensajes de error es más detallada.
Por otro lado, algunas ventajas de interpretar frente a compilar son:
  • Un intérprete necesita menos memoria que un compilador.
  • Permite una mayor interactividad con el código en tiempo de desarrollo (e incluso, en algunos casos, mientras se ejecuta el código).
Compilación en tiempo de ejecución
Para compensar los puntos débiles de ambas soluciones, también existe el llamado modelo de compilación en tiempo de ejecución (en inglés, Just-in-time-compiler, o "compilador justo a tiempo"). Este tipo de compilador, que a veces también se conoce por el término inglés compreter(acránimo de com piler e inter preter), rompe con el modelo habitual de compilación y traduce el código del programa durante el tiempo de ejecución, al igual que el intérprete. De esta forma, la alta velocidad de ejecución típica de los compiladores se complementa con la simplificación del proceso de desarrollo.

En un entorno de compilación en tiempo de ejecución, la compilación a bytecode es el primer paso, reduciendo el código fuente a una representación intermedia portable y optimizable. El bytecode se despliega en el sistema de destino. Cuando dicho código se ejecuta, el compilador en tiempo de ejecución lo traduce a código máquina nativo. Esto puede realizarse a nivel de fichero (programa) o de funciones, compilándose en este último caso el código correspondiente a una función justo cuando va a ejecutarse (de aqui el nombre de just-in-time, «justo a tiempo»).

El objetivo es combinar muchas de las ventajas de la compilación a código nativo y a bytecode: la mayoría del «trabajo pesado» de procesar el código fuente original y realizar optimizaciones básicas se realiza en el momento de compilar a bytecode, mucho antes del despliegue: así, la compilación a código máquina del programa resulta mucho más rápida que partiendo del código fuente.

El bytecode desplegado es portable, a diferencia del código máquina para cualquier arquitectura concreta. Los compiladores dinámicos son más fáciles de escribir, pues el compilador a bytecode ya realiza buena parte del trabajo.

Java es uno de los ejemplos más conocidos de lenguaje basado en compilación en tiempo de ejecución: el compilador JIT, que figura entre los componentes del Java Runtime Environment (JRE), mejora el rendimiento de las aplicaciones Java traduciendo el código de bytes en código máquina de manera dinámica.

Entornos de desarrollo
Los entornos de desarrollo, comúnmente conocidos como IDE (Entorno de Desarrollo Integrado, por sus siglas en inglés Integrated Development Environment), son herramientas que los programadores utilizan para escribir, depurar y compilar su código. Algunos aspectos importantes de los entornos de desarrollo incluyen:
  • Editor de Código: Los IDEs incluyen editores de código que ofrecen resaltado de sintaxis, sugerencias de autocompletado y otras características para mejorar la eficiencia de escritura.
  • Depuración: Los entornos de desarrollo proporcionan herramientas de depuración que permiten a los programadores detectar y corregir errores en su código de manera eficiente.
  • Gestión de Proyectos: Los IDEs suelen ofrecer funciones para organizar proyectos, gestionar bibliotecas y dependencias, y facilitar la colaboración en equipo.
  • Integración con Herramientas: Pueden integrar herramientas de control de versiones, compiladores, analizadores estáticos y otros recursos que hacen que el desarrollo sea más efectivo.
  • Personalización: Los entornos de desarrollo suelen ser altamente personalizables para adaptarse a las preferencias del programador.
  • Soporte Multiplataforma: Muchos IDEs se pueden utilizar en sistemas operativos como Windows, macOS y Linux.
Existen multitud de entornos de desarrollo. Algunos ejemplos son:


El poder analizar el código fuente es una práctica fundamental para mejorar la calidad, seguridad y eficiencia del desarrollo de software. Ayuda a prevenir errores, garantizar el cumplimiento de estándares y a mantener la integridad del código a lo largo del ciclo de desarrollo. Los entornos de desarrollo son herramientas vitales para escribir y analizar código, así como para realizar pruebas y auditorías de seguridad. Hoy en día la mayoría de los entornos de desarrollo permiten realizar el análisis estático de código mientras el desarrollador escribe el código.

Pruebas de software
De forma general las pruebas de software las podemos clasificar en función de cómo las ejecutamos:
  1. Pruebas manuales
  2. Pruebas automáticas
Otra clasificación se basa en la visibilidad del código:
  • Pruebas de caja blanca: los evaluadores tienen acceso completo al código fuente y conocimiento detallado de la arquitectura del sistema.
  • Pruebas de caja gris: los evaluadores tienen un conocimiento parcial del código fuente y la arquitectura del sistema. Combinan elementos de pruebas de caja blanca y caja negra.
  • Pruebas de caja negra: no se tiene acceso al código, los evaluadores no tienen conocimiento del código fuente subyacente, solo interactúan con la aplicación desde una perspectiva de usuario externo.
También podemos clasificarlas basándonos en lo que estamos midiendo y probando, teniendo muchos tipos distintos. las más comunes son:
  • Examen de la unidad o pruebas unitarias: valida que cada unidad de software funcione como se esperaba. Una unidad es el componente comprobable más pequeño de una aplicación. Normalmente, la unidad más pequeña suele ser una función o método. Se realizan típicamente por desarrolladores durante el proceso de desarrollo.
  • Pruebas de integración: verifican que distintos módulos que funcionan por separado funcionen de manera conjunta. Pueden ser pruebas de integración horizontal (pruebas entre componentes similares) o pruebas de integración vertical (pruebas entre capas de la aplicación).
  • Pruebas funcionales: se centran en verificar si el software cumple con sus especificaciones funcionales. Evalúan las funciones (no confundir con las pruebas unitarias) y características del software para garantizar que funcionen como se espera.
  • Pruebas de aceptación: verifican si todo el conjunto de software se comporta según lo esperado. Pueden ser pruebas de aceptación del usuario (UAT) realizadas por el cliente o pruebas de aceptación del negocio (BAT) realizadas por los analistas de negocio.
  • Pruebas de usabilidad: estas pruebas confirman la eficacia y facilidad con la que un usuario puede interactuar con un sistema o una aplicación web para llevar a cabo una tarea específica. Ayudan a garantizar que el software sea intuitivo y satisfaga las necesidades del usuario.
  • Pruebas de rendimiento: prueban el rendimiento del software con diferentes cargas de trabajo. Se utilizan para evaluar el rendimiento en condiciones de carga reales.
  • Pruebas de regresión: verifican si el código introducido rompe o degrada la funcionalidad del software original. Suelen realizarse cuando hacemos cambios en un programa.
  • Pruebas de Seguridad: estas pruebas evalúan la robustez del sistema ante posibles amenazas y vulnerabilidades. Garantizan que el software sea resistente a posibles ataques y cumpla con los estándares de seguridad.
Las pruebas de seguridad
Son esenciales para identificar y mitigar riesgos de seguridad en sistemas y aplicaciones, y son parte fundamental de la ciberseguridad en la protección de datos y activos críticos. Veamos las más típicas:
  • Pruebas de Análisis Estático (Static Analysis Testing): Analizan el código fuente o binario de una aplicación en busca de vulnerabilidades potenciales sin ejecutar el programa. Identifican problemas de seguridad a nivel de código y los IDEs suelen incluir extensiones para realizar dichas pruebas mientras los desarrolladores crean el código. Ejemplo de herramientas: Checkmarx, Fortify, SonarQube.
  • Pruebas de Análisis Dinámico (Dynamic Analysis Testing): Evalúan el software en tiempo de ejecución para detectar posibles amenazas y vulnerabilidades mientras interactúa con el sistema. Ejemplo de herramientas: AppScan, Veracode, Burp Suite.
  • Pruebas de Penetración (Penetration Testing): También conocidas como "pen tests", implican simular ataques controlados por parte de expertos en seguridad. El objetivo es identificar vulnerabilidades y puntos de entrada para posibles atacantes. Estas pruebas se realizan antes de poner en producción el software y periódicamente en el entorno de producción. Ejemplo de herramientas: Metasploit, Nmap, Burp Suite, OWASP ZAP.
  • Pruebas de Vulnerabilidad (Vulnerability Assessment): Identifican y evalúan vulnerabilidades conocidas en sistemas y aplicaciones. Utilizan escáneres automatizados o herramientas especializadas para detectar debilidades de seguridad. Proporcionan una lista de vulnerabilidades que necesitan ser corregidas. Ejemplo de herramientas: Nessus, OpenVAS, Qualys, Nikto.
  • Pruebas de Seguridad Web (Web Application Security Testing): Centradas en evaluar la seguridad de aplicaciones web y sitios web. Incluyen pruebas de inyección SQL, cross-site scripting (XSS), cross-site request forgery (CSRF), y otras vulnerabilidades específicas de la web. Ayudan a garantizar que las aplicaciones web sean resistentes a ataques comunes. Ejemplo de herramientas: OWASP ZAP, Burp Suite, Acunetix.
  • Pruebas de Seguridad de Aplicaciones Móviles (Mobile Application Security Testing): Se enfocan en evaluar aplicaciones móviles en busca de vulnerabilidades específicas de plataformas móviles. Verifican la seguridad en aspectos como el almacenamiento de datos, la autenticación, y la comunicación segura. Ejemplo de herramientas: MobSF, OWASP Mobile Security Testing Guide, Burp Suite.
  • Pruebas de Seguridad de Red (Network Security Testing): Evalúan la seguridad de la infraestructura de red, como firewalls, routers y conmutadores. Buscan posibles puntos débiles y vulnerabilidades en la red que podrían ser explotados. Ejemplo de herramientas: Wireshark, Nmap, Snort.
  • Pruebas de Autenticación y Autorización: Evalúan la autenticación y autorización de usuarios y sistemas en una aplicación o red. Aseguran que solo los usuarios autorizados tengan acceso a recursos específicos. Estas pruebas no se basan en herramientas específicas, sino en casos de prueba diseñados para evaluar estas funciones.
  • Pruebas de Seguridad de APIs (API Security Testing): Se centran en evaluar la seguridad de las interfaces de programación de aplicaciones (APIs) utilizadas para la comunicación entre sistemas. Verifican que las API sean seguras y resistentes a ataques. Ejemplo de herramientas: Postman, Swagger Inspector y OWASP API Security Top Ten.
  • Pruebas de Estrés y Carga (Stress and Load Testing): Evalúan cómo el sistema se comporta bajo cargas de trabajo intensas o condiciones de estrés. Identifican cuellos de botella y problemas de rendimiento que podrían afectar la seguridad. Ejemplo de herramientas: Apache JMeter, LoadRunner y Gatling.
Recomendaciones a la hora de realizar las pruebas
Como ya hemos comentado las pruebas del software son una parte muy importante del desarrollo del software y sobre todo para la ciberseguridad. Aquí te damos algunas recomendaciones a la hora de realizar las pruebas:
  • Cada caso de prueba debe definir el resultado de salida esperado.
  • El programador debe evitar probar sus propios programas, ya que desea (consciente o inconscientemente) demostrar que funcionan sin problemas.
  • Se debe inspeccionar a conciencia el resultado de cada prueba, y así, poder descubrir posibles síntomas de defectos.
  • Al generar casos de prueba, se deben incluir tanto datos de entrada válidos y esperados como no válidos e inesperados.
  • Las pruebas deben centrarse en dos objetivos (es habitual olvidar el segundo):
    • Probar que el programa hace lo que debe hacer.
    • Probar que el programa no hace lo que no debe hacer.
  • Todos los casos de prueba deben documentarse y diseñarse con cuidado.
Además, es recomendable que las pruebas de software sean, en la medida de lo posible, automatizadas ya que permiten verificar de manera eficiente la funcionalidad de un programa a través de scripts y herramientas de prueba.

Recomiendo la plataforma mouredev pro, creada por el ingeniero de software Brais Moure, ya qué no puedes romper lo que no entiendes, y esta plataforma ofrece la base de desarrollo ideal para cualquier pentester. Su catálogo incluye formación clave para la ciberseguridad, como el dominio de Bash y la terminal para entornos Linux, Python para la creación de scripts y exploits, JavaScript para auditar vulnerabilidades web como XSS, y SQL para entender a fondo las inyecciones de bases de datos. Con un enfoque totalmente práctico, sin falsas promesas y con precios accesibles, es el recurso perfecto en español para que tu audiencia deje de ser script kiddies y aprenda la lógica de programación necesaria para analizar código de forma avanzada.