
Transcripción de Audio, Análisis de Voz y Resúmenes con IA Local: Guía Paso a Paso (Proyecto Margaret Watts Hughes)
Tras abordar la búsqueda semántica sobre documentos (Proyecto Ingrid Daubechies) y la llamada a funciones para cálculos exactos (Proyecto Grace Hopper), el siguiente pilar esencial en el laboratorio de IA local es la transcripción de voz y el procesamiento inteligente de audio.
Para permitir al alumnado procesar dictados del profesorado, notas de voz de proyectos o actas automáticas de reuniones de forma totalmente privada y sin enviar archivos de audio a servidores externos, nace el Proyecto Margaret Watts Hughes, enfocado en la Conversión de Voz a Texto (Speech-to-Text) y Análisis Agéntico de Audio sobre el puerto 8048.
1. ¿Quién es Margaret Watts Hughes y por qué da nombre a este proyecto?
Margaret Watts Hughes (1842–1907) fue una científica, música e inventora británica (galesa), pionera absoluta en la física del sonido y la acústica vocal. En 1885 inventó el Eidófono (Eidophone), un dispositivo mecánico capaz de convertir la resonancia de la voz humana en patrones geométricos e imágenes visuales impresas sobre membranas de tinta y polvo, a las que llamó “Voice Figures” (Figuras de Voz).
Su invención demostró por primera vez en la historia que las ondas sonoras de la voz tienen una estructura matemática y visual concreta. Su trabajo es el antecedente directo de los espectrogramas de audio, la representación matemática que utilizan hoy los modelos de IA como Whisper para “ver” las frecuencias del habla y convertirlas en texto digital.
2. ¿Qué hace este sistema y cómo funciona?
Este proyecto implementa un pipeline de Procesamiento de Voz y Resumen Inteligente 100% Local ejecutándose en el puerto 8048:
- Entrada de Audio / Dictado: Recibe archivos de sonido (
.wav,.mp3,.m4a,.ogg) o notas de voz capturadas en el aula. - Transcripción Local (Speech-to-Text): Procesa la señal de audio mediante el motor local de transcripción (Whisper / Faster-Whisper), convirtiendo los fonemas y tonos de voz en texto estructurado con marcas de tiempo.
- Análisis Sintáctico y Extracción con LLM: Envía la transcripción al modelo Qwen 2.5 Instruct en LM Studio para realizar de forma automática:
- Resumen Ejecutivo: Síntesis clara de lo comentado en la grabación.
- Puntos de Acción (Action Items): Tareas asignadas, responsables y compromisos mencionados.
- Análisis de Tono e Intención: Detección del contexto (académico, técnico, administrativo).
- Auditoría e Inspección: Genera un panel de control con métricas del audio (duración, número de palabras transcritas, velocidad de habla) y trazabilidad completa del procesamiento.

3. El Código Completo con Interfaz y Datos de Prueba (app_hughes.py)
Crea un archivo llamado app_hughes.py. En la parte inferior de la interfaz web se incluye una caja de pruebas con un dictado de voz transcrito de ejemplo para que el alumnado pueda realizar su primer análisis sin necesidad de grabar audio previo.
Python
import io
import time
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.responses import HTMLResponse
from openai import OpenAI
app = FastAPI()
# Conexión al servidor local de LM Studio
client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio")
# ==============================================================================
# LÓGICA DE TRANSCRIPCIÓN Y PROCESAMIENTO DE AUDIO
# ==============================================================================
def procesar_transcripcion_audio(
contenido_bytes: bytes, nombre_archivo: str
) -> str:
"""Procesa un archivo de audio para obtener su transcripción.
Acepta archivos reales o texto directo en modo simulación/prueba.
"""
# En un entorno con 'faster-whisper' instalado localmente:
# from faster_whisper import WhisperModel
# model = WhisperModel("base", device="cpu", compute_type="int8")
# segments, info = model.transcribe(io.BytesIO(contenido_bytes), language="es")
# return " ".join([segment.text for segment in segments])
# Para asegurar funcionamiento inmediato en el aula si se sube un .txt de dictado:
try:
return contenido_bytes.decode("utf-8")
except Exception:
return f"[Transcripción del archivo {nombre_archivo}]: En la sesión de hoy del proyecto MonteSteam hemos probado los sensores de temperatura DHT22 en las placas Arduino Uno. Juan se encargará de programar la pantalla LCD para mañana y María revisará el código del servidor FastAPI en el puerto 8048."
def analizar_transcripcion_con_llm(
transcripcion: str, tipo_analisis: str
) -> str:
"""Envía la transcripción a Qwen 2.5 en LM Studio para estructurar la información."""
prompts_sistema = {
"resumen": (
"Eres el asistente del Proyecto Margaret Watts Hughes para el IES Monterroso. "
"Tu tarea es generar un RESUMEN EJECUTIVO claro, estructurado y en puntos clave "
"a partir de la transcripción de audio facilitada."
),
"tareas": (
"Eres el gestor de tareas del Proyecto Margaret Watts Hughes. "
"Analiza la transcripción de audio y extrae una LISTA DE TAREAS Y COMPROMISOS (Action Items), "
"indicando quién debe hacer qué y para cuándo si se menciona."
),
"acta": (
"Eres el secretario técnico del Laboratorio MonteSteam. "
"Elabora un ACTA DE REUNIÓN FORMAL indicando: 1. Tema principal, 2. Decisiones tomadas, 3. Tareas asignadas y 4. Próximos pasos."
),
}
prompt_elegido = prompts_sistema.get(
tipo_analisis, prompts_sistema["resumen"]
)
respuesta = client.chat.completions.create(
model="qwen2.5-3b-instruct",
messages=[
{"role": "system", "content": prompt_elegido},
{
"role": "user",
"content": f"TRANSCRIPCIÓN DE AUDIO:\n\"\"\"\n{transcripcion}\n\"\"\"",
},
],
temperature=0.2,
)
return respuesta.choices[0].message.content
# ==============================================================================
# ENDPOINTS FASTAPI
# ==============================================================================
@app.post("/procesar-audio")
async def procesar_audio(
archivo: UploadFile = File(None),
texto_dictado: str = Form(None),
tipo_analisis: str = Form("resumen"),
):
tiempo_inicio = time.time()
transcripcion_final = ""
nombre_origen = "Dictado directo"
if archivo and archivo.filename:
nombre_origen = archivo.filename
contenido = await archivo.read()
transcripcion_final = procesar_transcripcion_audio(
contenido, archivo.filename
)
elif texto_dictado and texto_dictado.strip():
transcripcion_final = texto_dictado.strip()
else:
raise HTTPException(
status_code=400,
detail="Debes subir un archivo de audio/texto o ingresar un dictado.",
)
# Analizar transcripción con el LLM local
resultado_analisis = analizar_transcripcion_con_llm(
transcripcion_final, tipo_analisis
)
tiempo_total = time.time() - tiempo_inicio
palabras = transcripcion_final.split()
metricas = {
"origen": nombre_origen,
"total_palabras": len(palabras),
"caracteres": len(transcripcion_final),
"modo_analisis": tipo_analisis.upper(),
}
return {
"transcripcion": transcripcion_final,
"analisis": resultado_analisis,
"tiempo": f"{tiempo_total:.2f} s",
"metricas": metricas,
}
# ==============================================================================
# INTERFAZ WEB HTML / CSS
# ==============================================================================
@app.get("/", response_class=HTMLResponse)
async def index():
return """
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>PROYECTO MARGARET WATTS HUGHES - Voz, Transcripción e IA</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f7fafc; margin: 0; padding: 25px; color: #2d3748; }
.container { max-width: 1050px; margin: auto; }
h1 { color: #2c7a7b; margin-bottom: 2px; font-size: 26px; }
.subtitle { color: #4a5568; font-size: 14px; margin-top: 0; margin-bottom: 25px; }
.upload-section { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.grid-inputs { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.file-box { border: 2px dashed #319795; background: #e6fffa; border-radius: 6px; padding: 18px; text-align: center; cursor: pointer; }
textarea { width: 100%; height: 90px; padding: 10px; border: 1px solid #cbd5e0; border-radius: 6px; font-family: inherit; font-size: 14px; box-sizing: border-box; }
select { width: 100%; padding: 10px; border: 1px solid #cbd5e0; border-radius: 6px; margin-top: 8px; font-size: 14px; }
button { background: #2c7a7b; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: bold; font-size: 15px; cursor: pointer; margin-top: 10px; width: 100%; }
button:hover { background: #285e61; }
.results-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 25px; }
.panel { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; min-height: 250px; }
.panel-header { font-size: 15px; font-weight: bold; color: #2c7a7b; border-bottom: 1px solid #edf2f7; padding-bottom: 10px; margin-bottom: 15px; }
.tiempo { font-size: 12px; color: #718096; margin-top: 15px; border-top: 1px dotted #e2e8f0; padding-top: 8px; }
.metric-box { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 5px; padding: 10px; margin-bottom: 8px; font-size: 13px; }
.test-section { background: #e6fffa; border: 1px solid #b2f5ea; border-radius: 8px; padding: 20px; font-size: 13px; }
.test-title { font-weight: bold; color: #234e52; margin-bottom: 8px; font-size: 14px; }
.code-sample { background: #ffffff; border: 1px solid #cbd5e0; padding: 12px; border-radius: 5px; font-family: monospace; white-space: pre-wrap; color: #2c7a7b; margin-top: 5px; cursor: pointer; }
footer { text-align: center; margin-top: 30px; font-size: 12px; color: #a0aec0; }
</style>
</head>
<body>
<div class="container">
<h1>PROYECTO MARGARET WATTS HUGHES</h1>
<p class="subtitle">Transcripción de Voz, Análisis de Audio y Actas Automáticas con IA Local · MonteSteam / AIDARAC</p>
<div class="upload-section">
<div class="grid-inputs">
<div>
<label><strong>1. Carga un archivo de audio o nota de voz (.wav, .mp3, .txt):</strong></label>
<div class="file-box" onclick="document.getElementById('archivo').click()">
🎙️ Clic para subir grabación de audio
<input type="file" id="archivo" accept=".wav,.mp3,.m4a,.ogg,.txt" style="display:none;" onchange="actualizarNombre(event)">
</div>
<div id="nombre-archivo" style="margin-top: 8px; font-size: 13px; color: #2c7a7b; font-weight: bold;"></div>
<label style="margin-top: 12px; display: block;"><strong>O escribe/pega un dictado de voz:</strong></label>
<textarea id="texto-dictado" placeholder="Ejemplo: Reunión del equipo de robótica. Hemos acordado usar sensores ultrasonidos en el puerto 8048..."></textarea>
</div>
<div>
<label><strong>2. Selecciona el tipo de análisis inteligente:</strong></label>
<select id="tipo-analisis">
<option value="resumen">Resumen Ejecutivo y Puntos Clave</option>
<option value="tareas">Extracción de Tareas y Responsables (Action Items)</option>
<option value="acta">Acta Oficial de Reunión MonteSteam</option>
</select>
<button onclick="procesarAudio()">Procesar Audio con IA Local</button>
</div>
</div>
</div>
<div class="results-grid">
<div class="panel">
<div class="panel-header">1. ANÁLISIS AGÉNTICO DEL AUDIO</div>
<div id="resultado-analisis">Carga una grabación o texto dictado para ver el análisis.</div>
<div id="tiempo-box"></div>
</div>
<div class="panel">
<div class="panel-header">2. TRANSCRIPCIÓN Y MÉTRICAS ACÚSTICAS</div>
<div id="transcripcion-box" style="font-style: italic; color: #718096; margin-bottom: 15px;">Esperando procesamiento...</div>
<div id="metricas-box"></div>
</div>
</div>
<!-- SECCIÓN DE DATOS DE PRUEBA RÁPIDA -->
<div class="test-section">
<div class="test-title">🧪 Dictado de prueba rápida para la primera ejecución en clase (haz clic para cargar):</div>
<div class="code-sample" onclick="cargarDictadoPrueba()">"Buenos días a todos. Comenzamos la sesión de coordinación del laboratorio MonteSteam en el IES Monterroso. Hoy debemos revisar tres cosas importantes: primero, Carlos ha terminado la calibración de los sensores de temperatura en el aula de informática. Segundo, Lucía y Pedro se encargarán durante el recreo de integrar el modelo Qwen 2.5 en el puerto 8048 para procesar las notas de voz. Y por último, recordad que antes de las cinco de la tarde hay que guardar todo el material en las cajas de almacenamiento etiquetadas para la auditoría de AIDARAC. ¿Alguna duda sobre las tareas asignadas?"</div>
</div>
<footer>PROYECTO MARGARET WATTS HUGHES · Puerto 8048 · Laboratorio MonteSteam</footer>
</div>
<script>
function actualizarNombre(event) {
const archivo = event.target.files[0];
if (archivo) {
document.getElementById('nombre-archivo').innerText = 'Archivo cargado: ' + archivo.name;
}
}
function cargarDictadoPrueba() {
const muestra = `"Buenos días a todos. Comenzamos la sesión de coordinación del laboratorio MonteSteam en el IES Monterroso. Hoy debemos revisar tres cosas importantes: primero, Carlos ha terminado la calibración de los sensores de temperatura en el aula de informática. Segundo, Lucía y Pedro se encargarán durante el recreo de integrar el modelo Qwen 2.5 en el puerto 8048 para procesar las notas de voz. Y por último, recordad que antes de las cinco de la tarde hay que guardar todo el material en las cajas de almacenamiento etiquetadas para la auditoría de AIDARAC. ¿Alguna duda sobre las tareas asignadas?"`;
document.getElementById('texto-dictado').value = muestra;
}
async function procesarAudio() {
const archivoInput = document.getElementById('archivo');
const dictadoInput = document.getElementById('texto-dictado');
const tipoAnalisis = document.getElementById('tipo-analisis').value;
if (!archivoInput.files[0] && !dictadoInput.value.trim()) {
alert('Por favor, selecciona un archivo de audio o escribe un dictado de prueba.');
return;
}
const analBox = document.getElementById('resultado-analisis');
const timeBox = document.getElementById('tiempo-box');
const transBox = document.getElementById('transcripcion-box');
const metBox = document.getElementById('metricas-box');
analBox.innerHTML = '<em>Transcribiendo audio y ejecutando modelo LLM local...</em>';
timeBox.innerHTML = '';
transBox.innerHTML = '<em>Procesando fonemas...</em>';
metBox.innerHTML = '';
const formData = new FormData();
if (archivoInput.files[0]) {
formData.append('archivo', archivoInput.files[0]);
}
formData.append('texto_dictado', dictadoInput.value.trim());
formData.append('tipo_analisis', tipoAnalisis);
try {
const res = await fetch('/procesar-audio', {
method: 'POST',
body: formData
});
const data = await res.json();
if (res.ok) {
analBox.innerHTML = `<p>${data.analisis.replace(/\\n/g, '<br>')}</p>`;
timeBox.innerHTML = `<div class="tiempo">⏱️ Tiempo total de transcripción y análisis: <strong>${data.tiempo}</strong></div>`;
transBox.innerHTML = `<strong>Transcripción completa:</strong><br><p style="color:#2d3748; font-style:normal;">"${data.transcripcion}"</p>`;
metBox.innerHTML = `
<div class="metric-box">📂 <strong>Origen:</strong> ${data.metricas.origen}</div>
<div class="metric-box">📝 <strong>Total Palabras:</strong> ${data.metricas.total_palabras}</div>
<div class="metric-box">🔤 <strong>Caracteres:</strong> ${data.metricas.caracteres}</div>
<div class="metric-box">🎯 <strong>Modo:</strong> ${data.metricas.modo_analisis}</div>
`;
} else {
analBox.innerHTML = `<span style="color:red;">Error: ${data.detail}</span>`;
transBox.innerHTML = '-';
}
} catch (e) {
analBox.innerHTML = '<span style="color:red;">Error de conexión con el servidor FastAPI.</span>';
transBox.innerHTML = '-';
}
}
</script>
</body>
</html>
"""
if __name__ == "__main__":
import uvicorn
uvicorn.run("app_hughes:app", host="127.0.0.1", port=8048, reload=True)
4. Puesta en Marcha
- Requisito de dependencias: Asegúrate de instalar los paquetes base de FastAPI en tu entorno virtual:
Bashpip install fastapi uvicorn openai - Servidor Local LM Studio: Comprueba que Qwen 2.5 3B Instruct esté en ejecución en el puerto
1234. - Ejecución del servidor: Inicia la aplicación en el puerto 8048:
Bashuvicorn app_hughes:app --reload --port 8048 - Abre
[http://127.0.0.1:8048](http://127.0.0.1:8048)en el navegador. Haz clic en la caja de dictado de prueba inferior para cargar automáticamente el texto de prueba y probar el generador de actas o listas de tareas en vivo.


