Salvatore Mosaico · 2026
Índice
- Introducción
- 1. Primer día: de [0,1) a [1,N]
- 2. Segundo día: un intervalo cualquiera
- 3. La respuesta de la inteligencia artificial
- 4. ¿Dónde se equivocó la IA?
- 5. Por qué la duplicación resulta peligrosa
- 6. La interpretación según MOSAICO
- 7. Una precisión importante
- Conclusión: la nueva competencia del programador
Introducción
Un programa puede dar siempre el resultado correcto y, aun así, estar mal diseñado. Esta lección representa un diálogo entre docente, estudiante e inteligencia artificial. La tarea parece sencilla: utilizar Math.random() de JavaScript para generar enteros aleatorios. El verdadero objetivo aparece al final.
El primer día construimos una función general que devuelve un entero de 1 a N. El segundo ampliamos el resultado al intervalo [a,b]. La IA ofrece una fórmula que funciona. El docente sorprende a la clase: las matemáticas son correctas, pero la solución incumple una decisión arquitectónica del proyecto.
Debemos distinguir cuatro criterios: sintaxis válida, corrección matemática, superación de pruebas y coherencia del diseño. Cuando una capacidad ya se ha aislado y probado en un nivel inferior, el nivel superior debe utilizarla en lugar de reproducir su lógica, si ese es el contrato del sistema.
No basta con obtener el resultado correcto. Un buen programador coloca cada responsabilidad donde corresponde. Los diálogos siguientes son simulaciones didácticas, no transcripciones de una conversación real.
1. Primer día: de [0,1) a [1,N]
El docente presenta Math.random(). Devuelve un número pseudoaleatorio mayor o igual que cero y menor que uno: 0 ≤ Math.random() < 1. En [0,1), el corchete incluye cero y el paréntesis excluye uno. Suponemos que N es un entero positivo y pequeño, como en estos ejemplos escolares.
Escena de clase: comprender el intervalo
Docente: Queremos un entero de 1 a N. ¿Qué ocurre si multiplicamos el resultado por N?
Estudiante: [0,1) se convierte en [0,N).
Docente: En el modelo matemático podemos acercarnos tanto como queramos a N, pero nunca alcanzarlo.
Estudiante: Math.floor produce 0, 1, 2, …, N−1.
Docente: ¿Cómo llegamos a 1, 2, …, N?
Estudiante: Basta con sumar 1.
function RandomNumero(N) {
return Math.floor(Math.random() * N) + 1;
}Multiplicamos por N, redondeamos hacia abajo y sumamos 1. Con N=6 simulamos un dado. En el modelo uniforme ideal, cada resultado corresponde a un subintervalo de longitud 1/6. La implementación real es pseudoaleatoria y aproximadamente uniforme: no demuestra un azar perfecto.
2. Segundo día: un intervalo cualquiera
Estudiante: ¿Cómo generamos un entero entre a y b, incluidos ambos extremos?
Docente: ¿Cuántos enteros contiene [a,b]?
Estudiante: Diría b−a.
Docente: Comprobemos [5,10]: 5, 6, 7, 8, 9, 10. Seis valores, pero 10−5 es cinco.
Estudiante: Entonces son b−a+1, porque contamos ambos extremos.
Para [5,10] necesitamos seis resultados. RandomNumero(10−5+1) devuelve un entero de 1 a 6. Para obtener 5, 6, 7, 8, 9, 10 desplazamos todos los valores cuatro posiciones: sumamos a−1.
3. La respuesta de la inteligencia artificial
Preguntada directamente en nuestra simulación, la IA propone una fórmula conocida:
function RandomRange(a, b) {
return Math.floor(Math.random() * (b - a + 1)) + a;
}La explicación matemática es correcta: Math.random() genera un valor en [0,1); multiplicar por b−a+1 da [0,b−a+1); Math.floor produce enteros de 0 a b−a; sumar a produce los enteros de a a b.
Los extremos también encajan: un valor aleatorio cero da a; uno suficientemente cercano a uno da b. Ambas fórmulas representan la misma transformación matemática. Sin embargo, dos llamadas separadas no tienen por qué dar el mismo número: realizan extracciones distintas.
El giro inesperado
IA: La función solicitada es Math.floor(Math.random() * (b−a+1)) + a.
Estudiante: La fórmula es correcta. Hemos terminado.
Docente: No. Respecto al diseño acordado, hay un error de diseño.
Estudiante: Pero los resultados son correctos. ¿Dónde está el error?
¿Puede existir un defecto importante aunque el programa devuelva los valores correctos?
4. ¿Dónde se equivocó la IA?
Mostrar la respuesta y la explicación
La fórmula no está equivocada. El problema consiste en reescribir la generación aleatoria ignorando RandomNumero(N), ya construida y probada. Reaparece Math.floor(Math.random() * …): se duplica conocimiento de bajo nivel.
RandomRange se convierte en otro lugar que conoce Math.random(). La versión coherente con el proyecto delega en RandomNumero(b−a+1) y suma a−1.
Docente: ¿La IA resolvió el problema recibido?
Estudiante: Sí, la función devuelve un número entre a y b.
Docente: ¿Respetó el proyecto existente?
Estudiante: No. Resolvió de nuevo lo que RandomNumero ya sabía hacer.
Docente: Exacto: en esta escena atendió la petición local sin aprovechar la historia y la arquitectura del sistema.
El defecto se aprecia al observar dependencias, mantenimiento y evolución, no al ejecutar una vez la función. No es una incapacidad inevitable de la IA: aportar contexto y una restricción explícita de reutilización puede orientar otra respuesta. Sigue siendo necesario comprobar su cumplimiento.
5. Por qué la duplicación resulta peligrosa
Supongamos que queremos registrar cada extracción, sustituir la fuente durante las pruebas o adoptar un generador adecuado para requisitos de seguridad. Modificamos la función básica:
function RandomNumero(N) {
const resultado = Math.floor(Math.random() * N) + 1;
console.log("Número generado:", resultado);
return resultado;
}La versión de RandomRange que usa RandomNumero recibe automáticamente el registro. La que llama directamente a Math.random() sigue otro camino y no registra nada. El mensaje recoge el valor intermedio de 1 a N: registrar también el resultado final de a a b exige una decisión explícita en el nivel superior.
Consecuencias arquitectónicas
- Duplicación: la misma decisión técnica aparece en varios lugares.
- Mantenimiento: hay que recordar y repetir cada cambio en todas las implementaciones.
- Incoherencia: unas funciones pueden seguir el comportamiento nuevo y otras el antiguo.
- Capacidad de prueba: resulta más difícil sustituir la fuente aleatoria en un único punto para obtener pruebas repetibles.
- Responsabilidad: el nivel superior conoce un detalle asignado al nivel básico.
No se duplican solamente caracteres, sino una decisión. Cuando cambia, cada copia puede convertirse en un error. La reutilización ayuda mientras la función básica conserva su contrato: un cambio incompatible sigue exigiendo revisar a quienes la llaman.
6. La interpretación según MOSAICO
MOSAICO construye software por niveles. Las funciones básicas encapsulan capacidades elementales verificadas; los niveles superiores las componen sin reproducir su funcionamiento interno.
- Base: RandomNumero(N) produce un entero aleatorio de 1 a N.
- Nivel superior: RandomRange(a,b) cuenta los valores de [a,b] y desplaza el resultado.
- Frontera: en este ejemplo solo RandomNumero conoce Math.random y Math.floor.
- Ventaja: los cambios internos compatibles con el contrato se propagan a quienes llaman a la función.
La fórmula directa atraviesa esa frontera. MOSAICO hace visible la jerarquía: RandomRange → RandomNumero → Math.random.
Una pregunta es: «¿Cuál es la fórmula más directa para obtener un entero entre a y b?». El diseñador pregunta: «¿Qué capacidad tiene ya el sistema y cómo puedo componerla para obtener el nuevo comportamiento?». Esa segunda pregunta transforma una colección de funciones en arquitectura.
7. Una precisión importante
En un programa aislado sin RandomNumero, la fórmula directa es adecuada para los ejemplos propuestos. No existe una prohibición universal de llamar a Math.random(). El error nace del contexto: habíamos construido deliberadamente un nivel básico para reutilizarlo.
Diseñar bien no consiste en añadir funciones intermedias por todas partes. Consiste en asignar claramente una responsabilidad y no duplicarla cuando ya se ha centralizado.
Podemos comprobar que a y b son enteros y que a≤b:
function RandomRange(a, b) {
if (!Number.isInteger(a) || !Number.isInteger(b)) {
throw new Error("a y b deben ser enteros");
}
if (a > b) {
throw new Error("a no puede ser mayor que b");
}
return RandomNumero(b - a + 1) + a - 1;
}La relación entre a y b corresponde a la función de intervalo. La función básica debe garantizar su propio contrato sobre N, incluso cuando se llama directamente. Es una versión didáctica, no una biblioteca general para todos los valores Number.
Nota técnica: límites y seguridad
Number.isInteger no garantiza un entero seguro. Una biblioteca real debe definir su dominio, verificar también la amplitud b−a+1 y considerar el redondeo en coma flotante. Comprobar únicamente enteros seguros no demuestra una distribución exactamente uniforme sobre intervalos enormes. Aquí usamos intervalos pequeños como [1,6], [5,10] y [−3,3].
Math.random no es criptográficamente seguro: estos ejemplos no deben generar contraseñas, tokens ni secretos. Centralizar el generador facilita sustituirlo, pero no hace automáticamente segura la transformación de sus resultados.
Conclusión: la nueva competencia del programador
Una petición local puede recibir una respuesta localmente correcta. Sin embargo, el sistema tiene una historia: funciones disponibles, responsabilidades asignadas, dependencias permitidas y decisiones que deben seguir centralizadas.
El programador debe preguntarse si la solución encaja en el proyecto. Se necesita memoria arquitectónica, conocimiento de las consecuencias y voluntad de no aceptar automáticamente código solo porque funciona.
La diferencia está entre «Sé calcular directamente el resultado» y «El sistema ya sabe hacer lo fundamental: debo reutilizarlo y añadir únicamente lo que falta».
El defecto no estaba en el número devuelto, sino en ignorar conocimiento ya organizado. El paso de RandomNumero(N) a RandomRange(a,b) es pequeño, pero representa MOSAICO: reutilizar no solo código, sino responsabilidades y decisiones.