Como crear aplicación de realidad aumentada que utiliza inteligencia artificial (Google Gemini)

Una de las combinaciones más emocionantes es la unión de la Realidad Aumentada (RA) y la Inteligencia Artificial (IA). Imagina poder apuntar la cámara de tu dispositivo a un objeto en el mundo real y, al instante, recibir información detallada generada por IA sobre él, directamente superpuesta en tu entorno. Este artículo desglosa un proyecto web que hace exactamente eso, utilizando MindAR para la RA y la API de Gemini para la IA conversacional.
En esta aplicación de realidad aumentada estamos utilizando Inteligencia Artificial (IA) más específicamente IA generativa, que tiene la capacidad de crear contenido nuevo, como texto. En este caso, la API de Gemini de Google actúa como el cerebro del sistema. Cuando un usuario pregunta sobre un objeto, Gemini genera una descripción o un dato interesante, actuando como un asistente de conocimiento instantáneo.
La combinación de estas dos tecnologías potencia la interacción del usuario con el mundo. Ya no solo vemos contenido digital, sino que podemos consultarlo y obtener respuestas inteligentes y contextualizadas sobre lo que estamos viendo, cerrando la brecha entre lo físico y lo digital.
Comencemos con el tutorial de cómo hacer esta app de realidad aumentada
- Antes de iniciar con el HTML, CSS y el JavaScript de la aplicación es de vital importancia que consigas inicialmente una API KEY. Esta permite que tu inteligencia artificial funcione. Para conseguirla deberás de ingresar a la web de Google AI Studio
- Después de ingresar a la web inicia sesión con tu cuenta de Google.
- Al día de hoy esta es la pantalla que aparece (Tal vez en el futuro cambie).

- Selecciona la opción GET API KEY
- luego en la parte superior derecha veras un botón que dice (+ Crear clave de API) da clic en el.
- Aparecerá una nueva ventana donde podrás leer Buscar Proyectos de Google Cloud y selecciona la opción de GEMINI API
- Da clic en el botón Crear Clave de API en un proyecto existente.
- listo! ya tendrás tu número de API, la cual deberás copiar en un blog de notas o en cualquier lugar pues luego la utilizaremos en el código JavaScript.
- NOTA: Es importante que no compartas esta clave con nadie.
- En esta aplicación vamos a utilizar el micrófono de nuestro dispositivo móvil Android o iOS, te recomiendo este post: Utiliza el micrófono de Android en Realidad Aumentada
- Si quieres agregar más elementos u opciones a la inteligencia artificial de la app te recomiendo este post: Lista desplegable en realidad aumentada para tus modelos 3D
Pasemos ahora si a la explicación del archivo HTML.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AR con MindAR y Texto IA</title>
<link rel="stylesheet" href="style.css">
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mind-ar@1.2.2/dist/mindar-image-aframe.prod.js"></script>
</head>
<body>
<div id="message-box"></div>
<a-scene id="arScene" mindar-image="imageTargetSrc: ./targets.mind; autoStart: true; uiLoading: no; uiError: no; videoSettings: { facingMode: environment };"
color-space="sRGB" renderer="colorManagement: true, physicallyCorrectLights: true"
vr-mode-ui="enabled: false" device-orientation-permission-ui="enabled: false">
<a-assets>
</a-assets>
<a-camera position="0 0 0" look-controls="enabled: false"></a-camera>
<a-entity mindar-image-target="targetIndex: 0">
<a-entity id="ar-text-container" position="0 0.25 0">
<a-plane id="text-background" width="1.2" height="0.4" color="#333333" opacity="0.7" position="0 0 -0.01"></a-plane>
<a-text id="ar-text-display" value="" width="1" align="center" color="#FFFFFF" position="0 0 0"></a-text>
</a-entity>
</a-entity>
</a-scene>
<div id="input-container">
<input type="text" id="object-input" placeholder="¿Qué quieres consultar? (ej. perro, sol, libro)">
<button id="place-button">
<span>Consultar y Mostrar ✨</span>
<div id="loading-spinner"></div>
</button>
<button id="tts-button">
<span>Leer Descripción 🔊</span>
</button>
</div>
<script src="script.js"></script>
</body>
</html>
¿Qué es todo esto? te lo explico rápidamente.
- <meta charset=»UTF-8″>: Esencial para asegurar que los caracteres especiales, como las tildes, se muestren correctamente en la página web.
Desafortunadamente en este caso A-Frame juega un papel importante a la hora de agregar el A-Text de la aplicación y utiliza un tipo de Font en ingles que no posee muchos de los caracteres que tenemos en Latinoamérica es por este motivo es que en el video de muestra de la app faltan las tildes.
Para solucionarlo puedes tendrías que agregar una fuente personalizada en A-Frame, te recomiendo leer su documentación en su página oficial. Introduction – A-Frame
- <a-scene>: El contenedor principal de toda la experiencia de A-Frame y MindAR.
- mindar-image=»imageTargetSrc: ./targets.mind; …»: Esta es la configuración clave para MindAR. Le indica dónde encontrar el archivo de marcadores (targets.mind) y cómo iniciar la cámara.
- <a-assets>: Un lugar para precargar recursos como imágenes, modelos 3D, archivos de fuentes personalizadas.
- <a-camera>: La cámara virtual a través de la cual «vemos» la escena AR. MindAR se encarga de su posicionamiento automáticamente.
- <a-entity mindar-image-target=»targetIndex: 0″>: Define el marcador que MindAR debe detectar. targetIndex: 0 se refiere al primer marcador en tu archivo targets.mind. Todo lo que esté dentro de este a-entity aparecerá superpuesto a ese marcador una vez detectado.
- <a-text id=»ar-text-display»> y <a-plane id=»text-background»>: Estos elementos A-Frame crean el texto 3D y su fondo, que se mostrarán en la Realidad Aumentada. El value del a-text se actualiza dinámicamente con la respuesta de la IA.
- input-container: Un div que contiene los elementos de la interfaz de usuario: un campo de texto (object-input), un botón para consultar (place-button) y un botón para activar el Texto a Voz (tts-button).
Iniciamos con el corazon de la App – JavaScript.
Este script gestiona la entrada del usuario, se comunica con la API de Gemini, actualiza el contenido AR y maneja el Texto a Voz.
const messageBox = document.getElementById('message-box');
const objectInput = document.getElementById('object-input');
const placeButton = document.getElementById('place-button');
const loadingSpinner = document.getElementById('loading-spinner');
const arTextContainer = document.getElementById('ar-text-container');
const arTextDisplay = document.getElementById('ar-text-display');
const arScene = document.getElementById('arScene');
const ttsButton = document.getElementById('tts-button');
let currentDescription = ""; // Almacena la descripción actual para el TTS
// Muestra mensajes temporales al usuario (ej. "Cargando...")
function showMessage(message, duration = 3000) {
messageBox.textContent = message;
messageBox.style.display = 'block';
setTimeout(() => {
messageBox.style.display = 'none';
}, duration);
}
// Actualiza el texto 3D en la escena AR
function displayTextOnMarker(text, objectName) {
arTextDisplay.setAttribute('value', text);
arTextContainer.setAttribute('visible', true); // Asegura que el contenedor sea visible
showMessage(`¡Información sobre "${objectName}" mostrada!`, 1500);
ttsButton.style.display = 'block'; // Muestra el botón de leer
currentDescription = text; // Guarda la descripción para TTS
}
// Maneja el clic en el botón "Consultar"
async function onPlaceButtonClick() {
const objectName = objectInput.value.trim();
if (!objectName) {
showMessage('Por favor, escribe lo que quieres consultar.', 2000);
return;
}
arTextContainer.setAttribute('visible', false); // Oculta el texto anterior
ttsButton.style.display = 'none'; // Oculta el botón TTS
const aiData = await getAIDataForObject(objectName); // Llama a la IA
if (aiData && aiData.description) {
displayTextOnMarker(aiData.description, objectName);
} else {
showMessage('No se pudo obtener la descripción de la IA. Inténtalo de nuevo.', 3000);
console.error('AI Data incomplete or invalid:', aiData);
}
}
// Función clave: Conexión con la API de Gemini
async function getAIDataForObject(objectName) {
loadingSpinner.style.display = 'inline-block'; // Muestra spinner
placeButton.disabled = true; // Deshabilita botón
showMessage('Consultando a la IA...', 5000);
try {
const prompt = `Genera una descripción creativa y concisa o un dato curioso sobre el objeto "${objectName}". Responde en español.`;
const payload = {
contents: [{ role: "user", parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "application/json",
responseSchema: {
type: "OBJECT",
properties: { "description": { "type": "STRING" } },
propertyOrdering: ["description"]
}
}
};
// ¡Importante! Reemplaza "TU_API_KEY_DE_GEMINI" con tu clave real.
const apiKey = "AQUI VA TU CLAVE API"; // NO COMPARTAS ESTA API CON NADIE
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
console.error('Error HTTP en la API de Gemini:', response.status, errorText);
showMessage(`Error ${response.status} al obtener datos de la IA: ${errorText.substring(0, 100)}...`, 8000);
return null;
}
const result = await response.json();
if (result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts && result.candidates[0].content.parts.length > 0) {
const jsonText = result.candidates[0].content.parts[0].text;
try {
const parsedJson = JSON.parse(jsonText);
console.log('Datos de IA generados con éxito:', parsedJson);
return parsedJson;
} catch (parseError) {
console.error('Error parsing Gemini JSON response:', parseError, jsonText);
showMessage('Error: La respuesta de la IA no es un JSON válido.', 5000);
return null;
}
} else if (result.error) {
console.error('Error de la API de Gemini:', result.error.message || 'Error desconocido');
showMessage(`Error de la IA: ${result.error.message || 'Error desconocido'}`, 5000);
return null;
} else {
console.error('Unexpected Gemini API response structure:', result);
showMessage('No se pudo obtener datos de la IA. Estructura inesperada.', 3000);
return null;
}
} catch (error) {
showMessage('Error al conectar con la IA. Revisa tu conexión y clave API.', 5000);
console.error('Error calling Gemini API:', error);
return null;
} finally {
loadingSpinner.style.display = 'none'; // Oculta spinner
placeButton.disabled = false; // Habilita botón
}
}
// Función para Texto a Voz (TTS) en español
function speakDescription(text) {
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'es-ES'; // Configura el idioma a español
window.speechSynthesis.speak(utterance);
} else {
showMessage('Tu navegador no soporta Texto a Voz.', 3000);
}
}
// Verifica la disponibilidad de la cámara al cargar
function checkMediaDevices() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
console.log('navigator.mediaDevices y getUserMedia están disponibles.');
showMessage('Dispositivo compatible con acceso a cámara.', 2000);
} else {
console.error('navigator.mediaDevices o getUserMedia NO están disponibles en este navegador.');
showMessage('Este navegador no soporta el acceso a la cámara.', 5000);
}
}
// Event listeners y mensajes iniciales
arScene.addEventListener('loaded', () => {
console.log('A-Frame Scene Loaded. Intentando iniciar MindAR.');
checkMediaDevices();
const mindarImageComponent = arScene.components['mindar-image'];
if (mindarImageComponent) {
console.log('MindAR component found. AR will auto-start.');
showMessage('Cámara lista. Apunte al marcador.', 4000);
} else {
console.error('MindAR component not found on a-scene.');
showMessage('Error: Componente MindAR no encontrado en la escena A-Frame.', 6000);
}
});
arScene.addEventListener('arReady', () => {
console.log('MindAR: Cámara lista y AR iniciado.');
showMessage('Cámara lista. Apunte al marcador.', 4000);
});
arScene.addEventListener('arError', (error) => {
console.error('MindAR Error:', error);
let errorMessage = 'Error al iniciar la cámara o AR. Revise permisos y HTTPS.';
if (error && error.name === 'NotAllowedError') {
errorMessage = 'Permiso de cámara denegado. Por favor, permita el acceso en la configuración del navegador/sistema.';
} else if (error && error.name === 'NotFoundError') {
errorMessage = 'No se encontró una cámara.';
} else if (error && error.message && error.message.includes('Failed to parse URL from')) {
errorMessage = 'Error al cargar targets.mind.';
}
showMessage(errorMessage, 6000);
});
window.onload = function () {
placeButton.addEventListener('click', onPlaceButtonClick);
ttsButton.addEventListener('click', () => speakDescription(currentDescription));
showMessage('Cargando aplicación AR. Por favor, espere y conceda permisos de cámara.', 5000);
};
- showMessage(): Una utilidad para mostrar mensajes temporales al usuario, ideal para dar retroalimentación sobre el estado de la aplicación (cargando, errores, etc.).
- displayTextOnMarker(): Esta función actualiza el contenido del elemento <a-text> en la escena AR con la descripción obtenida de la IA. También se encarga de mostrar el botón de Texto a Voz.
- onPlaceButtonClick(): El manejador de eventos principal cuando el usuario quiere hacer una consulta. Recoge la entrada, oculta el texto anterior y llama a getAIDataForObject().
- getAIDataForObject(objectName): La función crucial que se conecta a la API de Gemini.
- Construye un prompt (la pregunta para la IA) en español.
- Configura el payload para la solicitud, especificando que queremos una respuesta en formato JSON con una propiedad description de tipo STRING. Esto es fundamental para que la IA devuelva solo la información que necesitamos de forma estructurada.
- Realiza una solicitud fetch a la URL de la API de Gemini, incluyendo tu clave API.
- Maneja las respuestas exitosas y los posibles errores, parseando la respuesta JSON.
- ¡Recuerda reemplazar
const apiKey =""con tu propia clave API de Gemini!
- speakDescription(text): Utiliza la Web Speech API del navegador para convertir el texto de la descripción en voz, con utterance.lang = ‘es-ES’ para asegurar la pronunciación correcta en español.
- Manejadores de eventos de MindAR (arReady, arError): Proporcionan retroalimentación al usuario sobre el estado de la cámara y el proceso de inicio de la Realidad Aumentada, incluyendo mensajes de error útiles si hay problemas con los permisos o la carga del marcador.
Para terminar, pasemos al CSS.
/* Estilos básicos para el cuerpo y el lienzo */
body {
margin: 0;
overflow: hidden;
font-family: 'Inter', sans-serif; /* Fuente recomendada */
background-color: #1a202c; /* Color de fondo oscuro */
color: #e2e8f0; /* Color de texto claro */
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end; /* Alinea el contenido inferior */
min-height: 100vh; /* Asegura que ocupe toda la altura de la ventana */
}
a-scene {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 0; /* Detrás de la UI */
}
#message-box {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 8px;
font-size: 1rem;
text-align: center;
z-index: 1001;
display: none; /* Oculto por defecto */
max-width: 90%;
word-wrap: break-word;
}
#input-container {
position: fixed; /* Cambiado de relative a fixed */
bottom: 20px;
left: 50%; /* Centrar horizontalmente */
transform: translateX(-50%); /* Ajuste para el centrado */
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 10px;
background-color: rgba(26, 32, 44, 0.8);
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
width: 90%;
max-width: 400px;
box-sizing: border-box; /* Asegura que el padding se incluya en el ancho */
}
#object-input {
width: 100%;
padding: 10px;
border-radius: 8px;
border: 1px solid #4a5568;
background-color: #2d3748;
color: #e2e8f0;
font-size: 1rem;
box-sizing: border-box;
}
#object-input::placeholder {
color: #a0aec0;
}
#place-button {
padding: 10px 20px;
background-color: #63b3ed;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
width: 100%;
display: flex; /* Para alinear el spinner */
align-items: center;
justify-content: center;
}
#place-button:hover {
background-color: #4299e1;
}
#loading-spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid #fff;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
display: none;
margin-left: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#tts-button {
padding: 8px 15px;
background-color: #f6ad55; /* Naranja */
color: white;
border: none;
border-radius: 6px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.3s ease;
margin-top: 10px;
display: none; /* Oculto por defecto */
}
#tts-button:hover {
background-color: #ed8936;
}El manejo de la inteligencia artificial en las aplicaciones de realidad aumentada nos permite crear apps interactivas para nuestros clientes y de esta manera llevarnos al siguiente nivel de desarrollo de apps de RA o AR (Augmented Reality).
Si quieres todo el código en un solo archivo te recomiendo seguir la comunidad de Realidad Aumentada Empezando Desde Cero en el siguiente link de WhatsApp