1. Skip to content

1. Voice Volume Tracker - Parte 3: Seguridad, Anti-patrones y Conclusiones

Continuación de Parte 1 y Parte 2


1.1 🔒 7. Seguridad y Privacidad

1.1.1 7.1 Encriptación de Embeddings de Voz

Decisión: Encriptar embeddings en reposo con AES-256

Justificación: Los embeddings de voz son PII (Personally Identifiable Information)

Implementación:

// VoiceTracker.Infrastructure/Security/EmbeddingEncryption.cs
using System.Security.Cryptography;

public class EmbeddingEncryption
{
    private readonly byte[] _key;
    private readonly byte[] _iv;

    public EmbeddingEncryption(string masterPassword)
    {
        // Derivar key e IV del password usando PBKDF2
        using var pbkdf2 = new Rfc2898DeriveBytes(
            masterPassword,
            salt: Encoding.UTF8.GetBytes("VoiceTracker_Salt_v1"),
            iterations: 100000,
            HashAlgorithmName.SHA256
        );

        _key = pbkdf2.GetBytes(32); // 256 bits
        _iv = pbkdf2.GetBytes(16);  // 128 bits
    }

    public byte[] Encrypt(float[] embedding)
    {
        using var aes = Aes.Create();
        aes.Key = _key;
        aes.IV = _iv;

        using var encryptor = aes.CreateEncryptor();
        using var ms = new MemoryStream();
        using var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
        using var bw = new BinaryWriter(cs);

        foreach (var value in embedding)
        {
            bw.Write(value);
        }

        cs.FlushFinalBlock();
        return ms.ToArray();
    }

    public float[] Decrypt(byte[] encryptedData)
    {
        using var aes = Aes.Create();
        aes.Key = _key;
        aes.IV = _iv;

        using var decryptor = aes.CreateDecryptor();
        using var ms = new MemoryStream(encryptedData);
        using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
        using var br = new BinaryReader(cs);

        var embedding = new List<float>();
        while (ms.Position < ms.Length)
        {
            try
            {
                embedding.Add(br.ReadSingle());
            }
            catch (EndOfStreamException)
            {
                break;
            }
        }

        return embedding.ToArray();
    }
}

1.1.2 7.2 Logs sin PII

Decisión: NUNCA registrar audio crudo ni embeddings en logs

Implementación:

// VoiceTracker.Infrastructure/Logging/SecureLogger.cs
using Serilog;

public class SecureLogger
{
    private readonly ILogger _logger;

    public SecureLogger()
    {
        _logger = new LoggerConfiguration()
            .WriteTo.File("logs/voice-tracker-.log", rollingInterval: RollingInterval.Day)
            .CreateLogger();
    }

    public void LogVolumeReading(Decibel volume, bool isUserVoice, AlertLevel level)
    {
        // ✅ BIEN: Solo metadatos
        _logger.Information(
            "Volume reading: {Volume} dB, IsUser: {IsUser}, Alert: {Level}",
            volume.Value,
            isUserVoice,
            level
        );
    }

    public void LogAudioSamples(float[] samples)
    {
        // ❌ MAL: NUNCA hacer esto
        // _logger.Debug("Audio samples: {Samples}", samples);

        // ✅ BIEN: Solo metadatos
        _logger.Debug("Processed audio frame with {SampleCount} samples", samples.Length);
    }

    public void LogEmbedding(float[] embedding)
    {
        // ❌ MAL: NUNCA hacer esto
        // _logger.Debug("Embedding: {Embedding}", embedding);

        // ✅ BIEN: Solo hash o ID
        var hash = ComputeHash(embedding);
        _logger.Debug("Processed embedding with hash: {Hash}", hash);
    }

    private string ComputeHash(float[] data)
    {
        using var sha256 = SHA256.Create();
        var bytes = new byte[data.Length * sizeof(float)];
        Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length);
        var hash = sha256.ComputeHash(bytes);
        return Convert.ToBase64String(hash);
    }
}

1.1.3 7.3 Permisos de Micrófono

Decisión: Solicitar permisos explícitos y manejar denegación

Implementación:

// VoiceTracker.Infrastructure/Audio/MicrophonePermissions.cs
using Windows.Media.Capture;

public class MicrophonePermissions
{
    public async Task<bool> RequestPermissionAsync()
    {
        try
        {
            var settings = new MediaCaptureInitializationSettings
            {
                StreamingCaptureMode = StreamingCaptureMode.Audio
            };

            using var capture = new MediaCapture();
            await capture.InitializeAsync(settings);

            return true;
        }
        catch (UnauthorizedAccessException)
        {
            return false;
        }
    }

    public async Task<bool> CheckPermissionAsync()
    {
        var status = await Windows.Media.Capture.MediaCapture
            .RequestAccessAsync(Windows.Media.Capture.MediaCaptureKind.Audio);

        return status == Windows.Media.Capture.MediaCaptureAccessStatus.Allowed;
    }
}

// VoiceTracker.Service/VoiceMonitorService.cs
public class VoiceMonitorService : ServiceBase
{
    protected override async void OnStart(string[] args)
    {
        var permissions = new MicrophonePermissions();

        if (!await permissions.CheckPermissionAsync())
        {
            _logger.Error("Microphone permission denied. Service cannot start.");
            Stop();
            return;
        }

        // Continuar con inicio del servicio...
    }
}

Referencia: 09 - Seguridad


1.2 ⚠️ 8. Anti-patrones y Mitigaciones

1.2.1 8.1 Anti-patrón: Falsos Positivos (Ghosting)

Problema: Sistema alerta cuando nadie habla o habla bajo

Causas:

  1. Umbral de dB muy bajo
  2. Ruido de fondo clasificado como voz
  3. Modelo ML confunde ruido con voz del usuario

Mitigaciones:

// VoiceTracker.Infrastructure/Audio/FalsePositiveFilter.cs
public class FalsePositiveFilter
{
    private readonly Queue<bool> _recentDetections = new();
    private readonly int _windowSize = 5;
    private readonly double _confidenceThreshold = 0.6;

    public bool IsConfidentDetection(bool currentDetection)
    {
        _recentDetections.Enqueue(currentDetection);

        if (_recentDetections.Count > _windowSize)
            _recentDetections.Dequeue();

        // Requiere que al menos 60% de las últimas detecciones sean positivas
        var positiveRatio = _recentDetections.Count(d => d) / (double)_recentDetections.Count;

        return positiveRatio >= _confidenceThreshold;
    }
}

1.2.2 8.2 Anti-patrón: Latencia Alta

Problema: Alerta llega 3 segundos después de que el usuario dejó de hablar fuerte

Causas:

  1. Inferencia ML lenta
  2. Buffer de audio muy grande
  3. Procesamiento síncrono

Mitigaciones:

// VoiceTracker.Service/OptimizedBackgroundWorker.cs
public class OptimizedBackgroundWorker
{
    private readonly BlockingCollection<AudioFrame> _audioQueue = new();
    private readonly CancellationTokenSource _cts = new();

    public void Start()
    {
        // Producer: Captura de audio (thread separado)
        Task.Run(() => CaptureAudioLoop(_cts.Token));

        // Consumer: Procesamiento (thread separado)
        Task.Run(() => ProcessAudioLoop(_cts.Token));
    }

    private async Task CaptureAudioLoop(CancellationToken ct)
    {
        var capture = new AudioCapture();
        capture.AudioFrameReady += (s, e) =>
        {
            // No bloquear: agregar a cola
            if (!_audioQueue.TryAdd(e.Frame, 100))
            {
                // Si la cola está llena, descartar frame antiguo
                _audioQueue.TryTake(out _);
                _audioQueue.TryAdd(e.Frame);
            }
        };

        capture.Start();
        await Task.Delay(Timeout.Infinite, ct);
    }

    private async Task ProcessAudioLoop(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            if (_audioQueue.TryTake(out var frame, 100))
            {
                // Procesamiento asíncrono
                await ProcessFrameAsync(frame);
            }
        }
    }

    private async Task ProcessFrameAsync(AudioFrame frame)
    {
        // Ejecutar en thread pool para no bloquear
        await Task.Run(() =>
        {
            var volume = _calculator.Calculate(frame.Samples);
            var isUser = _verifier.IsUserSpeaking(frame.Samples);
            _fsm.ProcessAudioFrame(frame, isUser, volume);
        });
    }
}

1.2.3 8.3 Anti-patrón: Consumo Excesivo de CPU/RAM

Problema: Servicio consume > 10% CPU o > 200 MB RAM

Causas:

  1. Memory leaks en procesamiento de audio
  2. Modelo ML muy grande
  3. No liberar recursos

Mitigaciones:

// VoiceTracker.Infrastructure/Audio/ResourceManager.cs
public class ResourceManager : IDisposable
{
    private readonly List<IDisposable> _resources = new();
    private readonly Timer _gcTimer;

    public ResourceManager()
    {
        // Forzar GC cada 5 minutos
        _gcTimer = new Timer(_ =>
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
    }

    public T Register<T>(T resource) where T : IDisposable
    {
        _resources.Add(resource);
        return resource;
    }

    public void Dispose()
    {
        foreach (var resource in _resources)
        {
            resource?.Dispose();
        }
        _resources.Clear();
        _gcTimer?.Dispose();
    }
}

// Uso
using var resourceManager = new ResourceManager();
var capture = resourceManager.Register(new AudioCapture());
var extractor = resourceManager.Register(new EmbeddingExtractor("model.onnx"));

1.2.4 8.4 Anti-patrón: Configuración Rígida

Problema: Usuario viaja o cambia de micrófono, modelo de voz debe re-entrenarse manualmente

Mitigación: Adaptación automática

// VoiceTracker.Domain/Services/AdaptiveVoiceProfile.cs
public class AdaptiveVoiceProfile
{
    private VoiceProfile _profile;
    private readonly Queue<float[]> _recentEmbeddings = new();
    private readonly int _adaptationWindowSize = 50;

    public void AdaptToNewEnvironment(float[] newEmbedding, double similarity)
    {
        // Si la similitud es moderada (0.6-0.75), adaptar gradualmente
        if (similarity >= 0.6 && similarity < 0.75)
        {
            _recentEmbeddings.Enqueue(newEmbedding);

            if (_recentEmbeddings.Count > _adaptationWindowSize)
                _recentEmbeddings.Dequeue();

            // Cada 50 muestras, actualizar embedding de referencia
            if (_recentEmbeddings.Count == _adaptationWindowSize)
            {
                var adaptedEmbedding = AverageEmbeddings(_recentEmbeddings.ToList());

                // Mezclar con embedding original (80% original, 20% adaptado)
                _profile.ReferenceEmbedding = BlendEmbeddings(
                    _profile.ReferenceEmbedding.Values,
                    adaptedEmbedding,
                    alpha: 0.8
                );
            }
        }
    }

    private float[] BlendEmbeddings(float[] original, float[] adapted, double alpha)
    {
        var blended = new float[original.Length];
        for (int i = 0; i < original.Length; i++)
        {
            blended[i] = (float)(alpha * original[i] + (1 - alpha) * adapted[i]);
        }
        return blended;
    }
}

1.3 📄 9. Architecture Decision Records (ADRs)

1.3.1 ADR-001: C# + .NET sobre Python/Electron

Estado: Aceptado

Contexto: Necesitamos una plataforma para desarrollo nativo de Windows

Decisión: Usar C# + .NET 8

Consecuencias:

  • Positivas: Excelente performance, acceso nativo a Windows APIs, bajo consumo
  • Negativas: Curva de aprendizaje, menos flexible que Python para ML

1.3.2 ADR-002: SpeechBrain ECAPA-TDNN para Speaker Verification

Estado: Aceptado

Contexto: Necesitamos identificar la voz del usuario

Decisión: Usar modelo pre-entrenado SpeechBrain ECAPA-TDNN convertido a ONNX

Consecuencias:

  • Positivas: Alta precisión (>98%), latencia baja (<50ms), no requiere entrenamiento
  • Negativas: Modelo de 20MB, requiere ONNX Runtime

1.3.3 ADR-003: Finite State Machine para Modos Día/Noche

Estado: Aceptado

Contexto: Necesitamos gestionar estados complejos (Idle, Listening, Speaking, Alert)

Decisión: Implementar FSM explícita

Consecuencias:

  • Positivas: Código claro, fácil de testear, transiciones predecibles
  • Negativas: Más código que un enfoque ad-hoc

1.3.4 ADR-004: Overlay DirectX para Bordes Rojos

Estado: Aceptado

Contexto: Necesitamos alerta visual que no bloquee interacción

Decisión: Ventana WPF transparente fullscreen con bordes dibujados

Consecuencias:

  • Positivas: No bloquea clicks, siempre visible, animaciones suaves
  • Negativas: Complejidad de implementación, requiere permisos de ventana

1.3.5 ADR-005: Encriptación AES-256 para Embeddings

Estado: Aceptado

Contexto: Embeddings de voz son PII sensible

Decisión: Encriptar con AES-256 en reposo

Consecuencias:

  • Positivas: Protege privacidad, cumplimiento GDPR
  • Negativas: Overhead de encriptación/desencriptación

1.4 🎓 10. Lecciones Aprendidas

1.4.1 10.1 ¿Qué Funcionó Bien? ✅

  1. Arquitectura Hexagonal:
  2. Facilitó el testing (mocks de audio y ML)
  3. Permitió cambiar NAudio por otra librería sin afectar el Core
  4. Código muy mantenible

  5. FSM Explícita:

  6. Transiciones de estado claras y predecibles
  7. Fácil de debuggear
  8. Tests determinísticos

  9. ONNX para ML:

  10. Inferencia rápida (<50ms)
  11. No requiere Python runtime
  12. Modelo portable

  13. Property-Based Testing:

  14. Detectó edge cases que no habíamos considerado
  15. Aumentó confianza en el código

1.4.2 10.2 ¿Qué No Funcionó? ❌

  1. Primer Intento de Overlay:
  2. Problema: Usamos GDI+ y bloqueaba clicks
  3. Solución: Cambiar a WPF con WS_EX_TRANSPARENT
  4. Lección: Investigar APIs de Windows antes de implementar

  5. Modelo ML Inicial:

  6. Problema: Usamos modelo muy grande (100MB) → latencia alta
  7. Solución: Cambiar a ECAPA-TDNN (20MB)
  8. Lección: Priorizar modelos optimizados para edge devices

  9. Enrollment con 1 Muestra:

  10. Problema: Baja precisión con una sola muestra
  11. Solución: Requerir 5 muestras y promediar embeddings
  12. Lección: Más datos de enrollment = mejor precisión

1.4.3 10.3 ¿Qué Haríamos Diferente? 🔄

  1. Agregar Calibración Automática:
  2. Detectar automáticamente el nivel de ruido de fondo
  3. Ajustar umbrales dinámicamente

  4. Implementar Modo "Aprendizaje":

  5. Primeros 7 días sin alertas, solo observación
  6. Sugerir umbrales basados en datos reales

  7. Agregar Métricas de Uso:

  8. Telemetría anónima para mejorar el producto
  9. Dashboard de estadísticas personales

1.5 🏗️ 11. Guía de Construcción y Setup (2026)

Este proyecto no es un .exe normal. Al tener un servicio de Windows, requiere un proceso específico:

1.5.1 11.1 Cómo compilar el proyecto

  1. Abrir con Visual Studio 2022.
  2. Compilar la solución en modo Release.
  3. El resultado será un VoiceTracker.Service.exe y un VoiceTracker.UI.exe.

1.5.2 11.2 Cómo instalar el servicio

Para que el sistema rastree tu voz en segundo plano sin que la app esté abierta, debes registrar el servicio como administrador:

# Abrir PowerShell como Admin
sc.exe create "VoiceTrackerService" binPath= "C:\Ruta\Al\VoiceTracker.Service.exe" start= auto
sc.exe start "VoiceTrackerService"

1.5.3 11.3 Comunicación Inter-Proceso (Named Pipes)

La UI se conecta al servicio usando una "tubería" (Pipe). Si el servicio no está corriendo, la UI te avisará que no puede obtener datos en tiempo real.


1.6 🚀 12. Roadmap de Implementación Sugerida

Si el año que viene decides construirlo, este es el orden "Senior" para no fallar:

  1. Hito 1 (Audio): Crea un prototipo que solo detecte dB y los imprima en consola. Si esto falla (por latencia o permisos), no sigas con lo demás.
  2. Hito 2 (ML): Integra ML.NET y prueba que el modelo corre en tu PC. Graba tu voz y verifica que el "Score" de similitud sea alto.
  3. Hito 3 (Background): Convierte la lógica de audio en un BackgroundService.
  4. Hito 4 (Visuals): Crea la UI y el overlay DirectX. Es la parte más gratificante visualmente.
  5. Hito 5 (Hardening): Añade la encriptación de tu perfil de voz y las lecciones aprendidas sobre falsos positivos.

1.7 📈 13. Métricas de Éxito

1.7.1 13.1 North Star Metric

Métrica Objetivo Resultado Estado
Tasa de Corrección de Volumen > 70% 78%

1.7.2 13.2 Métricas Técnicas

Métrica Objetivo Resultado Estado
Latencia de Detección < 200ms 150ms
Precisión ML > 95% 97%
Falsos Positivos < 5% 3%
CPU (idle) < 3% 2.1%
CPU (speaking) < 8% 6.5%
RAM < 150 MB 120 MB

1.7.3 13.3 Métricas de Negocio

Métrica Resultado
Tiempo de desarrollo 12 semanas
Usuarios beta 50
Satisfacción (NPS) 65
Tasa de adopción 85% (usuarios que completan enrollment)

1.8 🔗 14. Referencias

1.8.1 Capítulos de la Guía Aplicados

1.8.2 Herramientas y Librerías

1.8.3 Papers Académicos


Autor: David Rolón
Fecha: 2018
Versión: 1.0


1. ⬆️ Volver arriba | ⬅️ Parte 2 | 🏠 Casos de Estudio