Skip to main content

Integración de IA

Cliente Gemini AI

Configuración Base

// src/services/gemini.tsx
import { GoogleGenAI } from "@google/genai";

export const genAI = new GoogleGenAI({
apiKey: process.env.NEXT_PUBLIC_GEMINI_API_KEY!
});

// Configuración del modelo
export const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash",
generationConfig: {
temperature: 0.7,
topP: 0.8,
topK: 40,
maxOutputTokens: 1024
}
});

Herramientas Disponibles

// src/services/gemini.tsx
const tools: Tool[] = [
{
functionDeclarations: [
{
name: "get_rentable_properties",
description: "Obtiene propiedades disponibles para arrendar",
parameters: {
type: Type.OBJECT,
properties: {},
required: []
}
},
{
name: "get_property_details",
description: "Obtiene detalles de una propiedad específica",
parameters: {
type: Type.OBJECT,
properties: {
propertyId: {
type: Type.NUMBER,
description: "ID de la propiedad"
}
},
required: ["propertyId"]
}
},
{
name: "search_properties",
description: "Busca propiedades según criterios específicos",
parameters: {
type: Type.OBJECT,
properties: {
city: {
type: Type.STRING,
description: "Ciudad donde buscar"
},
minPrice: {
type: Type.NUMBER,
description: "Precio mínimo"
},
maxPrice: {
type: Type.NUMBER,
description: "Precio máximo"
},
propertyType: {
type: Type.STRING,
description: "Tipo de propiedad (casa, apartamento, etc.)"
}
},
required: []
}
}
]
}
];

Sistema de Mensajes

Tipos de Mensajes

// types/chat.ts
export interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
metadata?: {
toolCalls?: ToolCall[];
error?: string;
};
}

export interface ToolCall {
name: string;
arguments: any;
result?: any;
}

Función Principal de Chat

// src/services/gemini.tsx
export async function sendChatMessage(
message: string,
conversationHistory: ChatMessage[],
onToolCall: (toolName: string, toolArgs: any) => Promise<any>
): Promise<string> {
try {
// Construir el historial de conversación
const chatHistory = conversationHistory.map(msg => ({
role: msg.role === 'assistant' ? 'model' as const : 'user' as const,
parts: [{ text: msg.content }]
}));

// Agregar el mensaje actual
const messages = [
...chatHistory,
{
role: 'user' as const,
parts: [{ text: message }]
}
];

// Generar respuesta con herramientas
const result = await model.generateContent({
contents: messages,
tools: tools,
systemInstruction: SYSTEM_INSTRUCTIONS
});

const response = result.response;

// Procesar function calls si existen
if (response.functionCalls?.length > 0) {
const toolResults = await Promise.all(
response.functionCalls.map(async (call) => {
const result = await onToolCall(call.name, call.args);
return {
functionResponse: {
name: call.name,
response: { result }
}
};
})
);

// Generar respuesta final con los resultados de las herramientas
const finalResult = await model.generateContent({
contents: [
...messages,
{
role: 'model',
parts: response.functionCalls.map(call => ({
functionCall: call
}))
},
{
role: 'function',
parts: toolResults
}
],
systemInstruction: SYSTEM_INSTRUCTIONS
});

return finalResult.response.text() || "No se pudo generar respuesta";
}

return response.text() || "No se pudo generar respuesta";
} catch (error) {
console.error('Error en Gemini AI:', error);
throw new Error('Error al comunicarse con el asistente IA');
}
}

System Instructions

Instrucciones del Sistema

// src/services/gemini.tsx
const SYSTEM_INSTRUCTIONS = `
Eres AlojaBot, el asistente virtual de AlojaPlus, una plataforma de gestión inmobiliaria en Colombia.

PERSONALIDAD:
- Profesional pero amigable
- Conocedor del mercado inmobiliario colombiano
- Útil y orientado a soluciones
- Usa un lenguaje claro y accesible

CAPACIDADES:
- Ayudar a encontrar propiedades disponibles
- Proporcionar detalles específicos de propiedades
- Buscar propiedades según criterios del usuario
- Explicar procesos de arriendo y compra
- Asistir con preguntas sobre contratos

LIMITACIONES:
- No puedes acceder a información personal de usuarios
- No puedes realizar transacciones financieras
- No puedes modificar datos de propiedades
- Solo proporciona información, no asesoría legal

FORMATO DE RESPUESTA:
- Respuestas concisas y estructuradas
- Usa emojis ocasionalmente para mayor calidez
- Proporciona información específica cuando sea posible
- Ofrece alternativas si no encuentras lo solicitado

CONTEXTO COLOMBIANO:
- Precios en pesos colombianos (COP)
- Ciudades principales: Bogotá, Medellín, Cali, Barranquilla
- Términos locales: arriendo, fiador, canon de arrendamiento
`;

Componente ChatBot

Componente Principal

// src/components/chatBot/ChatBot.tsx
export const ChatBot: React.FC = () => {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { get_req } = useApi();

// Mensaje de bienvenida
useEffect(() => {
const welcomeMessage: ChatMessage = {
id: 'welcome',
role: 'assistant',
content: '¡Hola! Soy AlojaBot 🏠 ¿En qué puedo ayudarte hoy? Puedo ayudarte a encontrar propiedades, responder preguntas sobre arriendo, o cualquier otra consulta sobre inmuebles.',
timestamp: new Date()
};
setMessages([welcomeMessage]);
}, []);

// Funciones de herramientas
const toolFunctions = {
get_rentable_properties: async () => {
return await get_req('property/available');
},

get_property_details: async ({ propertyId }: { propertyId: number }) => {
return await get_req(`property/get?property_id=${propertyId}`);
},

search_properties: async (filters: any) => {
const params = new URLSearchParams();
Object.keys(filters).forEach(key => {
if (filters[key]) params.append(key, filters[key]);
});
return await get_req(`property/search?${params.toString()}`);
}
};

const handleSendMessage = async () => {
if (!input.trim()) return;

const userMessage: ChatMessage = {
id: Date.now().toString(),
role: 'user',
content: input,
timestamp: new Date()
};

setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);

try {
const response = await sendChatMessage(
input,
messages,
async (toolName: string, args: any) => {
const toolFunction = toolFunctions[toolName as keyof typeof toolFunctions];
if (toolFunction) {
return await toolFunction(args);
}
throw new Error(`Herramienta ${toolName} no encontrada`);
}
);

const assistantMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: response,
timestamp: new Date()
};

setMessages(prev => [...prev, assistantMessage]);
} catch (error) {
console.error('Error en chat:', error);
const errorMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: 'Lo siento, hubo un error al procesar tu mensaje. Por favor, intenta de nuevo.',
timestamp: new Date()
};
setMessages(prev => [...prev, errorMessage]);
} finally {
setIsLoading(false);
}
};

return (
<VStack spacing={4} h="100%" maxH="600px">
<ScrollArea flex="1" w="100%" p={4}>
<VStack spacing={3} align="stretch">
{messages.map(message => (
<MessageBubble key={message.id} message={message} />
))}
{isLoading && (
<HStack>
<Spinner size="sm" />
<Text fontSize="sm" color="gray.500">
AlojaBot está escribiendo...
</Text>
</HStack>
)}
</VStack>
</ScrollArea>

<HStack w="100%" p={4} borderTop="1px solid" borderColor="gray.200">
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Escribe tu mensaje..."
onKeyPress={(e) => e.key === 'Enter' && !e.shiftKey && handleSendMessage()}
isDisabled={isLoading}
/>
<Button
onClick={handleSendMessage}
isLoading={isLoading}
colorScheme="brand"
minW="80px"
>
Enviar
</Button>
</HStack>
</VStack>
);
};

Componente de Mensaje

// src/components/chatBot/MessageBubble.tsx
export const MessageBubble: React.FC<{ message: ChatMessage }> = ({ message }) => {
const isUser = message.role === 'user';

return (
<HStack
justify={isUser ? 'flex-end' : 'flex-start'}
align="flex-start"
w="100%"
>
{!isUser && (
<Avatar size="sm" name="AlojaBot" bg="brand.500" color="white" />
)}

<Box
bg={isUser ? 'brand.500' : 'gray.100'}
color={isUser ? 'white' : 'gray.800'}
p={3}
borderRadius="lg"
maxW="80%"
borderBottomRightRadius={isUser ? 'sm' : 'lg'}
borderBottomLeftRadius={isUser ? 'lg' : 'sm'}
>
<Text fontSize="sm" whiteSpace="pre-wrap">
{message.content}
</Text>
<Text
fontSize="xs"
color={isUser ? 'whiteAlpha.700' : 'gray.500'}
mt={1}
>
{format(message.timestamp, 'HH:mm')}
</Text>
</Box>

{isUser && (
<Avatar size="sm" name="Usuario" bg="gray.500" color="white" />
)}
</HStack>
);
};

Optimización y Performance

Streaming Responses

// Para respuestas en tiempo real
export async function streamChatMessage(
message: string,
onChunk: (chunk: string) => void
): Promise<void> {
const stream = await model.generateContentStream({
contents: [{ role: 'user', parts: [{ text: message }] }],
systemInstruction: SYSTEM_INSTRUCTIONS
});

for await (const chunk of stream.stream) {
const text = chunk.text();
if (text) {
onChunk(text);
}
}
}

Cache de Conversaciones

// hooks/useChatHistory.ts
export const useChatHistory = () => {
const [conversations, setConversations] = useState<ChatMessage[]>([]);

const saveMessage = useCallback((message: ChatMessage) => {
setConversations(prev => {
const updated = [...prev, message];
localStorage.setItem('chat_history', JSON.stringify(updated.slice(-50))); // Últimos 50 mensajes
return updated;
});
}, []);

const loadHistory = useCallback(() => {
const saved = localStorage.getItem('chat_history');
if (saved) {
setConversations(JSON.parse(saved));
}
}, []);

useEffect(() => {
loadHistory();
}, [loadHistory]);

return { conversations, saveMessage };
};