Skip to main content
¿Primera vez?

Lee la guía de instalación primero para entender los conceptos básicos.

React & Next.js

Integración completa del widget de AIFindr en aplicaciones React y Next.js con hooks personalizados, manejo de estado y SSR.

React (CRA, Vite, etc.)

1. Script en index.html

Añade el script en public/index.html antes de </body>:

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mi App React</title>
</head>
<body>
<noscript>Necesitas JavaScript para ejecutar esta app.</noscript>
<div id="root"></div>

<!-- Widget de AIFindr -->
<script
src="https://hub.aifindr.ai/widget.js"
data-client-id="TU_CLIENT_ID"
defer
></script>
</body>
</html>

2. Hook personalizado

Crea hooks/useAIFindr.ts:

import { useEffect, useCallback, useRef } from 'react';

interface AIFindrWidget {
ready: (callback: () => void) => void;
open: () => void;
close: () => void;
toggle: () => void;
setContext: (context: Record<string, any>) => void;
mergeContext: (context: Record<string, any>) => void;
getContext: () => Record<string, any>;
on: (event: string, callback: Function) => void;
}

declare global {
interface Window {
AIFindrWidget?: AIFindrWidget;
}
}

interface UseAIFindrOptions {
context?: Record<string, any>;
autoSetContext?: boolean;
}

export function useAIFindr(options: UseAIFindrOptions = {}) {
const { context, autoSetContext = true } = options;
const isReady = useRef(false);
const widget = useRef<AIFindrWidget | null>(null);

useEffect(() => {
const initWidget = () => {
if (window.AIFindrWidget) {
widget.current = window.AIFindrWidget;

window.AIFindrWidget.ready(() => {
isReady.current = true;

if (autoSetContext && context) {
window.AIFindrWidget?.mergeContext(context);
}
});
} else {
// Reintentar si el widget no está disponible
setTimeout(initWidget, 100);
}
};

initWidget();
}, []);

// Actualizar contexto cuando cambie
useEffect(() => {
if (isReady.current && autoSetContext && context) {
window.AIFindrWidget?.mergeContext(context);
}
}, [context, autoSetContext]);

const open = useCallback(() => {
window.AIFindrWidget?.open();
}, []);

const close = useCallback(() => {
window.AIFindrWidget?.close();
}, []);

const toggle = useCallback(() => {
window.AIFindrWidget?.toggle();
}, []);

const setContext = useCallback((newContext: Record<string, any>) => {
window.AIFindrWidget?.setContext(newContext);
}, []);

const mergeContext = useCallback((newContext: Record<string, any>) => {
window.AIFindrWidget?.mergeContext(newContext);
}, []);

const getContext = useCallback(() => {
return window.AIFindrWidget?.getContext() || {};
}, []);

return {
open,
close,
toggle,
setContext,
mergeContext,
getContext,
isReady: isReady.current
};
}

Next.js

1. Script con Next.js Script

En pages/_app.tsx o app/layout.tsx:

import Script from 'next/script';

export default function App({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />

<Script
src="https://hub.aifindr.ai/widget.js"
data-client-id="TU_CLIENT_ID"
strategy="afterInteractive"
/>
</>
);
}

2. Provider Context

Crea context/AIFindrProvider.tsx:

import React, { createContext, useContext, useEffect, useState } from 'react';
import { useRouter } from 'next/router';

interface AIFindrContextType {
isReady: boolean;
open: () => void;
close: () => void;
toggle: () => void;
setContext: (context: Record<string, any>) => void;
mergeContext: (context: Record<string, any>) => void;
}

const AIFindrContext = createContext<AIFindrContextType | null>(null);

export function AIFindrProvider({ children }: { children: React.ReactNode }) {
const [isReady, setIsReady] = useState(false);
const router = useRouter();

useEffect(() => {
const initWidget = () => {
if (window.AIFindrWidget) {
window.AIFindrWidget.ready(() => {
setIsReady(true);

// Contexto inicial
window.AIFindrWidget.setContext({
framework: 'nextjs',
route: router.pathname,
timestamp: new Date().toISOString()
});
});
} else {
setTimeout(initWidget, 100);
}
};

initWidget();
}, []);

// Actualizar contexto en cambios de ruta
useEffect(() => {
if (isReady) {
window.AIFindrWidget?.mergeContext({
route: router.pathname,
query: router.query,
updated_at: new Date().toISOString()
});
}
}, [router.pathname, router.query, isReady]);

const contextValue: AIFindrContextType = {
isReady,
open: () => window.AIFindrWidget?.open(),
close: () => window.AIFindrWidget?.close(),
toggle: () => window.AIFindrWidget?.toggle(),
setContext: (context) => window.AIFindrWidget?.setContext(context),
mergeContext: (context) => window.AIFindrWidget?.mergeContext(context)
};

return (
<AIFindrContext.Provider value={contextValue}>
{children}
</AIFindrContext.Provider>
);
}

export function useAIFindrContext() {
const context = useContext(AIFindrContext);
if (!context) {
throw new Error('useAIFindrContext debe usarse dentro de AIFindrProvider');
}
return context;
}

Componentes

Trigger Component

// components/AIFindrTrigger.tsx
import React from 'react';

interface AIFindrTriggerProps {
children: React.ReactNode;
className?: string;
onClick?: () => void;
}

export function AIFindrTrigger({
children,
className = '',
onClick
}: AIFindrTriggerProps) {
return (
<button
id="ai-findr-trigger"
type="button"
className={className}
onClick={onClick}
>
{children}
</button>
);
}

Header con contexto

// components/Header.tsx
import { AIFindrTrigger } from './AIFindrTrigger';
import { useAIFindr } from '../hooks/useAIFindr';

export function Header() {
const { mergeContext } = useAIFindr({
context: {
component: 'header',
user_action: 'viewing_navigation'
}
});

const handleHelpClick = () => {
mergeContext({
help_source: 'header_button',
clicked_at: new Date().toISOString()
});
};

return (
<header className="header">
<nav>
<h1>Mi App</h1>
<AIFindrTrigger
className="help-button"
onClick={handleHelpClick}
>
¿Necesitas ayuda?
</AIFindrTrigger>
</nav>
</header>
);
}

Uso avanzado

Hook con estado local

// hooks/useAIFindrWithState.ts
import { useState, useCallback } from 'react';
import { useAIFindr } from './useAIFindr';

interface UserState {
isLoggedIn: boolean;
userType: 'free' | 'premium' | 'enterprise';
currentPlan?: string;
}

export function useAIFindrWithState(userState: UserState) {
const [isOpen, setIsOpen] = useState(false);

const { open, close, toggle, mergeContext } = useAIFindr({
context: {
user_logged_in: userState.isLoggedIn,
user_type: userState.userType,
current_plan: userState.currentPlan
}
});

const openWithState = useCallback(() => {
setIsOpen(true);
mergeContext({
opened_at: new Date().toISOString(),
user_state: userState
});
open();
}, [open, mergeContext, userState]);

const closeWithState = useCallback(() => {
setIsOpen(false);
close();
}, [close]);

return {
isOpen,
open: openWithState,
close: closeWithState,
toggle,
mergeContext
};
}

Componente de página con contexto específico

// pages/dashboard.tsx (Next.js) o components/Dashboard.tsx (React)
import { useEffect } from 'react';
import { useAIFindr } from '../hooks/useAIFindr';

export default function Dashboard() {
const { mergeContext, setContext } = useAIFindr();

useEffect(() => {
// Contexto específico del dashboard
setContext({
page: 'dashboard',
available_actions: ['view_analytics', 'manage_settings', 'export_data'],
user_permissions: ['read', 'write'],
last_visit: localStorage.getItem('lastDashboardVisit') || 'first_time'
});
}, [setContext]);

const handleAnalyticsView = () => {
mergeContext({
viewing: 'analytics',
timestamp: new Date().toISOString()
});
};

return (
<div className="dashboard">
<h1>Dashboard</h1>
<button onClick={handleAnalyticsView}>
Ver Analytics
</button>
{/* Resto del contenido */}
</div>
);
}

Hook para SSR/SSG (Next.js)

// hooks/useAIFindrSSR.ts
import { useEffect, useState } from 'react';

export function useAIFindrSSR() {
const [isClient, setIsClient] = useState(false);
const [isReady, setIsReady] = useState(false);

useEffect(() => {
setIsClient(true);
}, []);

useEffect(() => {
if (!isClient) return;

const initWidget = () => {
if (window.AIFindrWidget) {
window.AIFindrWidget.ready(() => {
setIsReady(true);
});
} else {
setTimeout(initWidget, 100);
}
};

initWidget();
}, [isClient]);

return {
isClient,
isReady,
widget: isClient && isReady ? window.AIFindrWidget : null
};
}

Troubleshooting React/Next.js

Problemas comunes

ProblemaSolución
Widget no se carga en SSRUsar useEffect y verificar typeof window !== 'undefined'
Hook se ejecuta antes del scriptAñadir lógica de retry en useAIFindr
Contexto no se actualizaUsar dependencias correctas en useEffect
Hydration mismatchUsar useAIFindrSSR para manejo SSR/CSR

Debug en desarrollo

// En desarrollo
if (process.env.NODE_ENV === 'development') {
useEffect(() => {
if (window.AIFindrWidget) {
window.AIFindrWidget.ready(() => {
console.log('AIFindr listo');
console.log('Contexto:', window.AIFindrWidget.getContext());
});
}
}, []);
}

Próximos pasos