JAAK Visage SDK

Manual Completo para Implementación de Detección Facial con el Componente Web @jaak.ai/visage

⏱️ Tiempo estimado: 30-45 minutos para configuración completa


🎯 ¿Qué aprenderás en este manual?

Este manual te enseñará a implementar y usar el componente web @jaak.ai/visage para detección facial en tiempo real desde cero. No necesitas conocimientos técnicos avanzados - solo sigue los pasos.

🎮 Demo en Vivo

Antes de empezar con la implementación, puedes probar el componente en funcionamiento:

👉 Demo Live - JAAK Visage

¿Qué puedes hacer en el demo?

  • ✅ Probar detección facial en tiempo real
  • ✅ Ver el comportamiento del componente
  • ✅ Verificar compatibilidad con tu dispositivo
  • ✅ Hacer debugg en tiempo real

📋 Antes de Empezar - Lista de Verificación

Asegúrate de tener estos elementos listos:

  • Node.js 16.0+ instalado (para proyectos npm)
  • Navegador moderno (Chrome 88+, Firefox 85+, Safari 14+)
  • Conexión HTTPS (requerida para acceso a cámara)
  • Editor de código
  • Acceso a cámara web funcional

🗂️ Índice de Contenidos

SecciónQué harásTiempo
Paso 1Instalar y configurar el componente5 min
Paso 2Implementación básica en HTML15 min
Paso 3Configurar eventos correctos10 min
Paso 4Implementar en frameworks10 min
Paso 5Probar funcionamiento5 min

PASO 1: Instalar y Configurar el Componente

🎯 Objetivo

Instalar el componente @jaak.ai/visage e configurar el entorno.

✅ Métodos de Instalación

1.1 Instalación vía NPM (Recomendado)

npm install @jaak.ai/visage

1.2 Instalación vía CDN

<script type="module" src="https://unpkg.com/@jaak.ai/visage/dist/jaak-visage-webcomponent/jaak-visage-webcomponent.esm.js"></script>

1.3 Requisitos Técnicos

RequisitoVersión¿Obligatorio?
NavegadoresChrome 88+, Firefox 85+, Safari 14+
HTTPSProtocolo seguroSí (en producción)
JavaScriptES2017+

PASO 2: Implementación Básica en HTML

🎯 Objetivo

Crear tu primera implementación funcional del componente.

2.1 HTML Completo Funcional

<!DOCTYPE html>
<html dir="ltr" lang="es">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JAAK Visage - Detección Facial</title>
    
    <script type="module" src="https://unpkg.com/@jaak.ai/visage/dist/jaak-visage-webcomponent/jaak-visage-webcomponent.esm.js"></script>
    
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        
        .status {
            padding: 15px;
            margin: 10px 0;
            border-radius: 8px;
            background: #e3f2fd;
        }
        
        .status.success { background: #e8f5e8; }
        .status.error { background: #ffebee; }
        
        .controls {
            margin: 20px 0;
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        
        button {
            padding: 12px 24px;
            border: none;
            border-radius: 6px;
            background: #2196f3;
            color: white;
            cursor: pointer;
        }
        
        button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
        
        jaak-visage {
            width: 100%;
            max-width: 640px;
            display: block;
            margin: 20px auto;
            border-radius: 10px;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🎥 Detector Facial JAAK Visage</h1>
        
        <div class="status" id="statusDiv">
            <strong>Estado:</strong> <span id="statusText">Inicializando...</span>
        </div>
        
        <div class="controls">
            <button id="startBtn" disabled>🚀 Iniciar</button>
            <button id="stopBtn" disabled>⏹️ Detener</button>
            <button id="helpBtn" disabled>❓ Ayuda</button>
        </div>
        
        <jaak-visage
            id="faceDetector"
            debug="true"
            camera="auto">
        </jaak-visage>
        
        <div id="results" style="display: none;">
            <h3>📊 Resultados</h3>
            <div id="captureData"></div>
        </div>
    </div>

    <script>
        // Variables globales
        let jaakVisage = null;
        let isComponentReady = false;
        
        // Referencias DOM
        const statusText = document.getElementById('statusText');
        const statusDiv = document.getElementById('statusDiv');
        const startBtn = document.getElementById('startBtn');
        const stopBtn = document.getElementById('stopBtn');
        const helpBtn = document.getElementById('helpBtn');
        
        // Función para actualizar estado
        function updateStatus(message, type = 'info') {
            statusText.textContent = message;
            statusDiv.className = `status ${type}`;
        }
        
        // Función para habilitar botones
        function updateButtons() {
            startBtn.disabled = !isComponentReady;
            stopBtn.disabled = !isComponentReady;
            helpBtn.disabled = !isComponentReady;
        }
        
        // Inicializar componente correctamente
        async function initializeJaakVisage() {
            // ✅ IMPORTANTE: Esperar a que el componente esté definido
            await customElements.whenDefined('jaak-visage');
            
            jaakVisage = document.getElementById('faceDetector');
            
            if (!jaakVisage) {
                updateStatus('Error: No se encontró el componente', 'error');
                return;
            }
            
            jaakVisage.addEventListener('statusUpdated', handleStatusUpdated);
            jaakVisage.addEventListener('captureCompleted', handleCaptureCompleted);
            jaakVisage.addEventListener('error', handleError);
            
            // 🔍 Debug: Interceptar todos los eventos
            const originalDispatchEvent = jaakVisage.dispatchEvent.bind(jaakVisage);
            jaakVisage.dispatchEvent = function(event) {
                console.log('Evento desde jaak-visage:', event.type, event.detail);
                return originalDispatchEvent(event);
            };
            
            // Event listeners de botones
            startBtn.addEventListener('click', handleStart);
            stopBtn.addEventListener('click', handleStop);
            helpBtn.addEventListener('click', handleShowHelp);
            
            updateStatus('Componente inicializado', 'info');
        }
        
        // ✅ MANEJADOR CORRECTO para statusUpdated
        function handleStatusUpdated(event) {
            const { status, message } = event.detail;
            console.log('Status actualizado:', status, message);
            
            // Estados posibles: 'inactive', 'ready', 'active', etc.
            if (status === 'ready' || status === 'inactive') {
                isComponentReady = true;
                updateStatus('✅ Componente listo para usar', 'success');
                updateButtons();
            } else if (status === 'active') {
                updateStatus('🎥 Detector activo', 'info');
            } else if (status === 'error') {
                updateStatus(`❌ Error: ${message}`, 'error');
            }
        }
        
        function handleCaptureCompleted(event) {
            const data = event.detail;
            updateStatus('✅ Captura completada', 'success');
            displayResults(data);
        }
        
        function handleError(event) {
            const error = event.detail;
            updateStatus(`❌ Error: ${error.message || 'Error desconocido'}`, 'error');
        }
        
        // Manejadores de botones
        async function handleStart() {
            try {
                updateStatus('🚀 Iniciando detector...', 'info');
                await jaakVisage.start();
            } catch (error) {
                updateStatus(`❌ Error: ${error.message}`, 'error');
            }
        }
        
        async function handleStop() {
            try {
                updateStatus('⏹️ Deteniendo...', 'info');
                await jaakVisage.stop();
                document.getElementById('results').style.display = 'none';
            } catch (error) {
                updateStatus(`❌ Error: ${error.message}`, 'error');
            }
        }
        
        async function handleShowHelp() {
            try {
                await jaakVisage.showHelp();
            } catch (error) {
                updateStatus(`❌ Error: ${error.message}`, 'error');
            }
        }
        
        function displayResults(data) {
            const resultsDiv = document.getElementById('results');
            const captureDataDiv = document.getElementById('captureData');
            
            let html = `
                <p><strong>Timestamp:</strong> ${new Date(data.timestamp).toLocaleString()}</p>
                <p><strong>Confianza:</strong> ${(data.confidence * 100).toFixed(2)}%</p>
            `;
            
            if (data.imageData) {
                html += `
                    <p><strong>Imagen:</strong></p>
                    <img src="${data.imageData}" alt="Captura" style="max-width: 300px; border-radius: 8px;">
                `;
            }
            
            captureDataDiv.innerHTML = html;
            resultsDiv.style.display = 'block';
        }
        
        // ✅ Inicializar cuando la página se carga
        window.addEventListener('load', initializeJaakVisage);
    </script>
</body>
</html>

PASO 3: Configurar Eventos Correctos

🎯 Objetivo

Configurar correctamente los eventos reales que emite el componente.

📝 Eventos Reales del Componente

3.1 Evento Principal: statusUpdated

jaakVisage.addEventListener('statusUpdated', (event) => {
    const { status, message } = event.detail;
    
    // Estados posibles:
    switch(status) {
        case 'ready':
        case 'inactive':
            // Componente listo para usar
            enableControls();
            break;
        case 'active':
            // Detector funcionando
            showActiveState();
            break;
        case 'error':
            // Error ocurrido
            showError(message);
            break;
    }
});

3.2 Otros Eventos Disponibles

// Captura completada
jaakVisage.addEventListener('captureCompleted', (event) => {
    const data = event.detail;
});

// Errores
jaakVisage.addEventListener('error', (event) => {
    const error = event.detail;
    console.error('Error:', error.message);
});

3.3 Inicialización Correcta

async function initializeComponent() {
    // ✅ IMPORTANTE: Esperar a que el componente esté definido
    await customElements.whenDefined('jaak-visage');
    
    const jaakVisage = document.querySelector('jaak-visage');
    
    // Ahora es seguro agregar event listeners
    jaakVisage.addEventListener('statusUpdated', handleStatusUpdated);
}

PASO 4: Implementar en Frameworks

📍 React

import React, { useRef, useEffect, useState } from 'react';
import { defineCustomElements } from '@jaak.ai/visage/loader';

defineCustomElements();

const FaceDetector = () => {
    const jaakVisageRef = useRef(null);
    const [isReady, setIsReady] = useState(false);
    const [capturedData, setCapturedData] = useState(null);

    useEffect(() => {
        const initComponent = async () => {
            // ✅ Esperar a que esté definido
            await customElements.whenDefined('jaak-visage');
            const component = jaakVisageRef.current;

            component.addEventListener('statusUpdated', (event) => {
                const { status } = event.detail;
                if (status === 'ready' || status === 'inactive') {
                    setIsReady(true);
                }
            });

            component.addEventListener('captureCompleted', (event) => {
                setCapturedData(event.detail);
            });
        };

        initComponent();
    }, []);

    const handleStart = async () => {
        try {
            await jaakVisageRef.current.start();
        } catch (error) {
            console.error('Error:', error);
        }
    };

    return (
        <div>
            <h2>Detector Facial React</h2>
            
            <button onClick={handleStart} disabled={!isReady}>
                Iniciar
            </button>
            
            <jaak-visage
                ref={jaakVisageRef}
                debug="false"
                camera="auto">
            </jaak-visage>

            {capturedData && (
                <div>
                    <h3>Resultado</h3>
                    <img src={capturedData.imageData} alt="Captura" />
                </div>
            )}
        </div>
    );
};

export default FaceDetector;

📍 Angular

// Component
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import { defineCustomElements } from '@jaak.ai/visage/loader';

defineCustomElements();

@Component({
    selector: 'app-face-detector',
    template: `
        <div>
            <h2>Detector Facial Angular</h2>
            
            <button (click)="start()" [disabled]="!isReady">
                Iniciar
            </button>
            
            <jaak-visage #jaakVisage debug="false" camera="auto">
            </jaak-visage>
            
            <div *ngIf="capturedData">
                <h3>Resultado</h3>
                <img [src]="capturedData.imageData" alt="Captura">
            </div>
        </div>
    `,
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class FaceDetectorComponent implements AfterViewInit {
    @ViewChild('jaakVisage') jaakVisage!: ElementRef;
    
    isReady = false;
    capturedData: any = null;

    async ngAfterViewInit() {
        // ✅ Esperar a que esté definido
        await customElements.whenDefined('jaak-visage');
        
        const component = this.jaakVisage.nativeElement;
        
        component.addEventListener('statusUpdated', (event: any) => {
            const { status } = event.detail;
            if (status === 'ready' || status === 'inactive') {
                this.isReady = true;
            }
        });

        component.addEventListener('captureCompleted', (event: any) => {
            this.capturedData = event.detail;
        });
    }

    async start() {
        try {
            await this.jaakVisage.nativeElement.start();
        } catch (error) {
            console.error('Error:', error);
        }
    }
}

PASO 5: Probar Funcionamiento

🎯 Objetivo

Verificar que todo funciona correctamente.

✅ Lista de Verificación

ElementoDescripción
Componente cargaElemento aparece en DOM
Event statusUpdatedSe dispara correctamente
Permisos cámaraNavegador solicita permisos
Detección facialDetecta y captura rostros

🔍 Debug y Troubleshooting

Verificar eventos

// Interceptar todos los eventos del componente
const jaakVisage = document.querySelector('jaak-visage');
const originalDispatchEvent = jaakVisage.dispatchEvent.bind(jaakVisage);

jaakVisage.dispatchEvent = function(event) {
    console.log('📡 Evento:', event.type, event.detail);
    return originalDispatchEvent(event);
};

Problemas comunes

ProblemaSolución
Eventos no se disparan✅ Usar await customElements.whenDefined('jaak-visage')
isReady no funciona✅ Cambiar a statusUpdated
Cámara no funciona✅ Verificar HTTPS y permisos

📚 Referencia

🔧 Métodos Principales

MétodoDescripciónEjemplo
start()Inicia el detectorawait jaakVisage.start()
stop()Detiene el detectorawait jaakVisage.stop()
showHelp()Muestra ayudaawait jaakVisage.showHelp()

📡 Eventos

EventoDatos (event.detail)Cuándo se dispara
statusUpdated{status, message}Cambio de estado del componente
captureCompleted{timestamp, confidence, imageData}Captura exitosa
error{message, code?}Error en operación

⚙️ Propiedades

PropiedadTipoDefectoEjemplo
debugbooleanfalsedebug="true"
camerastring"auto"camera="front"

🛠️ Solución de Problemas

🚨 Problemas Frecuentes

El componente no responde

// ✅ Solución: Esperar definición
async function fixComponentIssue() {
    await customElements.whenDefined('jaak-visage');
    // Ahora es seguro usar el componente
}

Eventos no funcionan

// ❌ Incorrecto
jaakVisage.addEventListener('isReady', handler); // Este evento NO existe

// ✅ Correcto
jaakVisage.addEventListener('statusUpdated', (event) => {
    const { status } = event.detail;
    if (status === 'ready') {
        // Componente listo
    }
});

Cámara no se activa

CausaSolución
HTTP en producción✅ Usar HTTPS
Permisos bloqueados✅ Verificar configuración del navegador
Cámara ocupada✅ Cerrar otras aplicaciones

📞 ¿Necesitas Ayuda?

🆘 Información para soporte

  • Descripción del problema: Qué intentas hacer vs qué sucede
  • Código relevante: Fragmentos de implementación
  • Consola del navegador: Screenshots de errores
  • Entorno: Navegador, versión, dispositivo

🔍 Debug avanzado

// Herramientas de debugging
function enableDebugMode() {
    const jaakVisage = document.querySelector('jaak-visage');
    
    // Habilitar logs
    jaakVisage.setAttribute('debug', 'true');
    
    // Interceptar eventos
    const events = ['statusUpdated', 'captureCompleted', 'error'];
    events.forEach(eventName => {
        jaakVisage.addEventListener(eventName, (event) => {
            console.log(`🔍 [${eventName}]:`, event.detail);
        });
    });
    
    // Información del sistema
    console.log('📱 UserAgent:', navigator.userAgent);
    console.log('📹 MediaDevices:', 'mediaDevices' in navigator);
    console.log('🔧 WebComponents:', 'customElements' in window);
}

enableDebugMode();

¡Listo! 🎉

Has implementado exitosamente el componente de detección facial JAAK Visage con la configuración correcta de eventos y mejores prácticas.