De Excel a Agente de IA en 30 Minutos
Tu hoja de cálculo de Excel ha evolucionado durante años. Maneja casos especiales, implementa reglas complejas de negocio e incorpora conocimiento profundo del dominio. Ahora quieres que un agente de IA la use.
La mayoría de los tutoriales te dirán que "simplemente exportes a CSV" o "reconstruyas en Python." Vamos a hacer algo mejor: dar a tu agente de IA acceso directo al motor de cálculo de Excel.
Lo que Estamos Construyendo
Un agente de IA de servicio al cliente que puede:
- Calcular cotizaciones precisas usando tu Excel de precios
- Verificar fechas de entrega con tu modelo de logística
- Aplicar descuentos basados en reglas complejas de negocio
- Manejar casos especiales exactamente como lo hace tu equipo
Todo usando tus archivos de Excel existentes. No se requiere reconstrucción.
Prerrequisitos
# Necesitarás:
npm install langchain @langchain/openai
# o
pip install langchain openai
# Y una cuenta de SpreadAPI (el nivel gratuito funciona)
# Regístrate en https://spreadapi.ioPaso 1: Preparar tu Excel para IA
Tu Estructura de Excel
PricingModel.xlsx
├── Entradas
│ ├── B2: Código de Producto
│ ├── B3: Cantidad
│ ├── B4: Tipo de Cliente
│ └── B5: Región
├── Cálculos (Ocultos de la IA)
│ ├── Fórmulas VLOOKUP complejas
│ ├── Matrices de descuentos
│ └── Reglas de negocio
└── Salidas
├── E10: Precio Base
├── E11: Monto de Descuento
├── E12: Precio Final
└── E13: Fecha de EntregaSubir a SpreadAPI
- Iniciar sesión en el Dashboard de SpreadAPI
- Crear nuevo servicio llamado "pricing-model"
- Subir tu Excel
- Definir interfaz:
{
"inputs": {
"productCode": "B2",
"quantity": "B3",
"customerType": "B4",
"region": "B5"
},
"outputs": {
"basePrice": "E10",
"discount": "E11",
"finalPrice": "E12",
"deliveryDate": "E13"
}
}Paso 2: Crear el Agente de IA
Agente Básico con Function Calling
import { ChatOpenAI } from '@langchain/openai';
import { SpreadAPITool } from './spreadapi-tool';
// Definir la herramienta de cálculo de Excel
const pricingTool = {
name: "calculate_pricing",
description: "Calcular precios precisos usando el modelo de precios de la empresa. Úsalo siempre que necesites cotizar precios o verificar descuentos.",
parameters: {
type: "object",
properties: {
productCode: {
type: "string",
description: "Código de producto (ej. 'PRO-001')"
},
quantity: {
type: "number",
description: "Número de unidades"
},
customerType: {
type: "string",
enum: ["standard", "premium", "enterprise"],
description: "Tipo de cuenta del cliente"
},
region: {
type: "string",
enum: ["US", "EU", "APAC"],
description: "Región del cliente"
}
},
required: ["productCode", "quantity", "customerType", "region"]
},
execute: async (params) => {
// Llamar a SpreadAPI
const response = await fetch('https://api.spreadapi.io/v1/services/pricing-model/execute', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SPREADAPI_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ inputs: params })
});
const result = await response.json();
return result.outputs;
}
};
// Crear el agente de IA
const model = new ChatOpenAI({
modelName: "gpt-4",
temperature: 0
});
const tools = [pricingTool];
const modelWithTools = model.bind({ tools });Implementar la Lógica del Agente
class CustomerServiceAgent {
constructor(model, tools) {
this.model = model;
this.tools = tools;
this.conversation = [];
}
async respond(userMessage) {
// Agregar mensaje del usuario a la conversación
this.conversation.push({
role: 'user',
content: userMessage
});
// Obtener respuesta de IA con posibles llamadas a herramientas
const response = await this.model.invoke(this.conversation);
// Manejar llamadas a herramientas
if (response.tool_calls && response.tool_calls.length > 0) {
for (const toolCall of response.tool_calls) {
const tool = this.tools.find(t => t.name === toolCall.name);
if (tool) {
// Ejecutar el cálculo de Excel
const result = await tool.execute(toolCall.arguments);
// Agregar resultado de herramienta a la conversación
this.conversation.push({
role: 'tool',
content: JSON.stringify(result),
tool_call_id: toolCall.id
});
}
}
// Obtener respuesta final después de la ejecución de herramientas
const finalResponse = await this.model.invoke(this.conversation);
this.conversation.push({
role: 'assistant',
content: finalResponse.content
});
return finalResponse.content;
}
// No se necesitan llamadas a herramientas
this.conversation.push({
role: 'assistant',
content: response.content
});
return response.content;
}
}Paso 3: Patrones Listos para Producción
Patrón 1: Agente Multi-Herramienta
// Agregar múltiples herramientas basadas en Excel
const tools = [
{
name: "calculate_pricing",
description: "Calcular precios de productos y descuentos",
spreadapiService: "pricing-model",
execute: spreadapiExecutor("pricing-model")
},
{
name: "check_inventory",
description: "Verificar disponibilidad de productos y tiempos de entrega",
spreadapiService: "inventory-tracker",
execute: spreadapiExecutor("inventory-tracker")
},
{
name: "calculate_shipping",
description: "Calcular costos de envío y fechas de entrega",
spreadapiService: "logistics-calculator",
execute: spreadapiExecutor("logistics-calculator")
}
];
// Función auxiliar para ejecución de SpreadAPI
function spreadapiExecutor(serviceName) {
return async (params) => {
const response = await fetch(
`https://api.spreadapi.io/v1/services/${serviceName}/execute`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SPREADAPI_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ inputs: params })
}
);
if (!response.ok) {
throw new Error(`Fallo en cálculo de Excel: ${response.statusText}`);
}
const result = await response.json();
return result.outputs;
};
}Patrón 2: Agente Consciente del Contexto
class ContextAwareAgent {
constructor() {
this.customerContext = {};
this.calculationCache = new Map();
}
async handleQuery(query, customerId) {
// Cargar contexto del cliente
if (!this.customerContext[customerId]) {
this.customerContext[customerId] = await this.loadCustomerData(customerId);
}
const context = this.customerContext[customerId];
// Mejorar prompt con contexto
const enhancedPrompt = `
Información del Cliente:
- Tipo: ${context.customerType}
- Región: ${context.region}
- Historial de Compras: ${context.totalPurchases} pedidos
Consulta del Usuario: ${query}
Instrucciones:
- Usa la herramienta calculate_pricing para cualquier cotización de precios
- Aplica automáticamente el tipo de cliente apropiado
- Considera su región para cálculos de envío
`;
return await this.respond(enhancedPrompt);
}
async loadCustomerData(customerId) {
// Cargar desde tu base de datos
return {
customerType: 'enterprise',
region: 'US',
totalPurchases: 47
};
}
}Patrón 3: Validación y Manejo de Errores
class RobustAgent {
async executeToolSafely(tool, params) {
try {
// Validar entradas antes de enviar a Excel
const validation = this.validateInputs(tool.name, params);
if (!validation.valid) {
return {
error: `Entrada inválida: ${validation.message}`,
suggestion: validation.suggestion
};
}
// Verificar caché primero
const cacheKey = `${tool.name}:${JSON.stringify(params)}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Ejecutar con timeout
const result = await Promise.race([
tool.execute(params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout de cálculo')), 5000)
)
]);
// Cachear resultados exitosos
this.cache.set(cacheKey, result);
// Validar salida
if (result.finalPrice < 0) {
return {
error: 'Resultado de cálculo inválido',
suggestion: 'Por favor verifica el código de producto y la cantidad'
};
}
return result;
} catch (error) {
console.error('Fallo en ejecución de herramienta:', error);
// Respuesta de respaldo
return {
error: 'No se puede calcular en este momento',
suggestion: 'Por favor intenta de nuevo o contacta soporte',
reference: error.message
};
}
}
validateInputs(toolName, params) {
if (toolName === 'calculate_pricing') {
if (params.quantity < 1) {
return {
valid: false,
message: 'La cantidad debe ser al menos 1',
suggestion: 'Por favor especifica una cantidad válida'
};
}
if (!params.productCode.match(/^[A-Z]{3}-\d{3}$/)) {
return {
valid: false,
message: 'Formato de código de producto inválido',
suggestion: 'Los códigos de producto deben ser como ABC-123'
};
}
}
return { valid: true };
}
}Paso 4: Capacidades Avanzadas del Agente
Capacidad 1: Cálculos Multi-Paso
const complexWorkflowTool = {
name: "quote_with_options",
description: "Generar una cotización completa con múltiples opciones de producto",
execute: async (params) => {
const { products, customerType, region } = params;
// Calcular precios para cada producto
const quotes = await Promise.all(
products.map(async (product) => {
const pricing = await spreadapiExecutor('pricing-model')({
productCode: product.code,
quantity: product.quantity,
customerType,
region
});
const shipping = await spreadapiExecutor('logistics-calculator')({
productCode: product.code,
quantity: product.quantity,
region,
expedited: product.expedited || false
});
return {
product: product.code,
quantity: product.quantity,
pricing,
shipping,
total: pricing.finalPrice + shipping.cost
};
})
);
// Calcular descuento de paquete si aplica
if (quotes.length > 1) {
const bundleResult = await spreadapiExecutor('bundle-calculator')({
products: products.map(p => p.code),
quantities: products.map(p => p.quantity),
customerType
});
return {
individualQuotes: quotes,
bundleDiscount: bundleResult.discount,
bundleTotal: bundleResult.total
};
}
return { quotes };
}
};Capacidad 2: Explicaciones y Razonamiento
class ExplainableAgent {
async respondWithExplanation(query) {
const response = await this.model.invoke([
{
role: 'system',
content: `Eres un agente de servicio al cliente útil.
Al usar herramientas de precios, siempre explica:
1. Qué factores afectaron el precio
2. Qué descuentos se aplicaron
3. Por qué esta es la mejor opción para el cliente`
},
{
role: 'user',
content: query
}
]);
// Procesar llamadas de herramientas y agregar explicaciones
if (response.tool_calls) {
const explanations = [];
for (const toolCall of response.tool_calls) {
const result = await this.executeTool(toolCall);
// Generar explicación basada en resultados
if (toolCall.name === 'calculate_pricing') {
const discount = result.basePrice - result.finalPrice;
const discountPercent = (discount / result.basePrice * 100).toFixed(1);
explanations.push({
calculation: toolCall.name,
explanation: `
Precio base: $${result.basePrice}
${discount > 0 ? `Descuento aplicado: $${discount} (${discountPercent}%)` : 'No hay descuento aplicable'}
Precio final: $${result.finalPrice}
Entrega antes del: ${result.deliveryDate}
`
});
}
}
// Obtener respuesta final con explicaciones
const finalResponse = await this.model.invoke([
...this.conversation,
{
role: 'system',
content: `Incluye estos detalles de cálculo en tu respuesta: ${JSON.stringify(explanations)}`
}
]);
return finalResponse.content;
}
return response.content;
}
}Capacidad 3: Comparación de Escenarios
const scenarioTool = {
name: "compare_scenarios",
description: "Comparar diferentes escenarios de compra para encontrar la mejor opción",
execute: async (params) => {
const scenarios = [
{
name: "Compra Individual",
params: {
quantity: params.quantity,
customerType: params.customerType
}
},
{
name: "Compra a Granel",
params: {
quantity: params.quantity * 3,
customerType: params.customerType
}
},
{
name: "Contrato Anual",
params: {
quantity: params.quantity * 12,
customerType: 'enterprise' // Mejora automática
}
}
];
const results = await Promise.all(
scenarios.map(async (scenario) => {
const pricing = await spreadapiExecutor('pricing-model')({
...params,
...scenario.params
});
return {
scenario: scenario.name,
totalQuantity: scenario.params.quantity,
unitPrice: pricing.finalPrice / scenario.params.quantity,
totalPrice: pricing.finalPrice,
savings: (params.quantity * (pricing.basePrice / scenario.params.quantity)) - pricing.finalPrice
};
})
);
// Encontrar la mejor opción
const bestOption = results.reduce((best, current) =>
current.unitPrice < best.unitPrice ? current : best
);
return {
scenarios: results,
recommendation: bestOption,
potentialSavings: results[0].totalPrice - bestOption.totalPrice
};
}
};Paso 5: Implementación y Monitoreo
Configuración de Producción
// config/agent.js
export const agentConfig = {
model: {
name: process.env.MODEL_NAME || 'gpt-4',
temperature: 0,
maxTokens: 1000,
timeout: 30000
},
spreadapi: {
baseUrl: process.env.SPREADAPI_URL || 'https://api.spreadapi.io/v1',
apiKey: process.env.SPREADAPI_KEY,
timeout: 5000,
retries: 3
},
caching: {
ttl: 300, // 5 minutos
maxSize: 1000
},
monitoring: {
logLevel: process.env.LOG_LEVEL || 'info',
metricsEnabled: true,
tracingEnabled: process.env.NODE_ENV === 'production'
}
};Monitoreo y Analíticas
class MonitoredAgent {
constructor(config) {
this.metrics = {
totalRequests: 0,
toolCalls: {},
errors: {},
responseTime: []
};
}
async handleRequest(query) {
const startTime = Date.now();
const requestId = generateRequestId();
try {
console.log(`[${requestId}] Procesando consulta:`, query);
const response = await this.agent.respond(query);
const duration = Date.now() - startTime;
this.metrics.responseTime.push(duration);
this.metrics.totalRequests++;
console.log(`[${requestId}] Completado en ${duration}ms`);
// Enviar a analíticas
await this.sendAnalytics({
requestId,
duration,
toolsUsed: this.agent.lastToolCalls,
success: true
});
return response;
} catch (error) {
const errorType = error.name || 'Desconocido';
this.metrics.errors[errorType] = (this.metrics.errors[errorType] || 0) + 1;
console.error(`[${requestId}] Error:`, error);
await this.sendAnalytics({
requestId,
error: error.message,
success: false
});
throw error;
}
}
getMetrics() {
const avgResponseTime =
this.metrics.responseTime.reduce((a, b) => a + b, 0) /
this.metrics.responseTime.length;
return {
totalRequests: this.metrics.totalRequests,
averageResponseTime: avgResponseTime,
toolUsage: this.metrics.toolCalls,
errorRate: Object.values(this.metrics.errors).reduce((a, b) => a + b, 0) /
this.metrics.totalRequests
};
}
}Errores Comunes y Soluciones
Error 1: Sobrecargar el Agente
// Malo: Dar demasiada libertad al agente
const badPrompt = "Ayuda al cliente con cualquier cosa que necesite";
// Bueno: Límites y capacidades claras
const goodPrompt = `Eres un agente de servicio al cliente especializado en:
1. Precios de productos (usa la herramienta calculate_pricing)
2. Disponibilidad de inventario (usa la herramienta check_inventory)
3. Estimaciones de envío (usa la herramienta calculate_shipping)
Para otras solicitudes, explica cortésmente en qué puedes ayudar.`;Error 2: No Manejar Errores de Excel
// Manejo robusto de errores
const toolWithErrorHandling = {
execute: async (params) => {
try {
const result = await spreadapiCall(params);
// Validar resultados de Excel
if (result.outputs.error) {
return {
success: false,
error: 'Error de cálculo en Excel',
details: result.outputs.error,
suggestion: 'Por favor verifica el código de producto e intenta de nuevo'
};
}
return { success: true, ...result.outputs };
} catch (error) {
if (error.status === 422) {
return {
success: false,
error: 'Parámetros de entrada inválidos',
suggestion: 'Por favor verifica el formato de tu código de producto'
};
}
throw error; // Re-lanzar errores inesperados
}
}
};Error 3: Ignorar el Rendimiento
// Optimización del rendimiento
class OptimizedAgent {
constructor() {
this.cache = new LRUCache({ max: 500, ttl: 1000 * 60 * 5 });
this.batchQueue = [];
this.batchTimer = null;
}
async calculatePricing(params) {
// Verificar caché primero
const cacheKey = JSON.stringify(params);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Agrupar solicitudes similares
return new Promise((resolve) => {
this.batchQueue.push({ params, resolve });
if (!this.batchTimer) {
this.batchTimer = setTimeout(() => this.processBatch(), 50);
}
});
}
async processBatch() {
const batch = this.batchQueue.splice(0, 50); // Procesar hasta 50 a la vez
const results = await spreadapiExecutor('pricing-model').batch(
batch.map(item => item.params)
);
results.forEach((result, index) => {
const { params, resolve } = batch[index];
this.cache.set(JSON.stringify(params), result);
resolve(result);
});
this.batchTimer = null;
}
}Probar tu Agente
// test/agent.test.js
describe('Agente de Servicio al Cliente', () => {
let agent;
beforeEach(() => {
agent = new CustomerServiceAgent();
});
test('debería calcular precios con precisión', async () => {
const response = await agent.respond(
"¿Cuál es el precio de 100 unidades de PRO-001 para un cliente enterprise en EE.UU.?"
);
expect(response).toContain('precio');
expect(response).toMatch(/\$[0-9,]+/);
});
test('debería manejar códigos de producto inválidos', async () => {
const response = await agent.respond(
"Precio para producto XYZ"
);
expect(response).toContain('código de producto válido');
});
test('debería comparar escenarios cuando se le pida', async () => {
const response = await agent.respond(
"¿Debería comprar 10 unidades ahora o esperar a un pedido a granel?"
);
expect(response).toContain('escenario');
expect(response).toContain('recomendación');
});
});Lista de Verificación para Lanzamiento
- [ ] Modelos de Excel subidos a SpreadAPI
- [ ] Mapeos de entrada/salida definidos
- [ ] Claves API almacenadas de forma segura
- [ ] Prompt del sistema del agente refinado
- [ ] Descripciones de herramientas claras y específicas
- [ ] Manejo de errores implementado
- [ ] Estrategia de caché en su lugar
- [ ] Monitoreo y registro configurados
- [ ] Limitación de velocidad habilitada
- [ ] Cobertura de pruebas > 80%
- [ ] Pruebas de carga completadas
- [ ] Respuestas de respaldo definidas
- [ ] Documentación actualizada
Próximos Pasos
- Empezar Simple: Un modelo de Excel, una herramienta, agente básico
- Agregar Inteligencia: Conciencia del contexto, flujos de trabajo multi-paso
- Escalar: Múltiples modelos, caché, monitoreo
- Optimizar: Ajuste de rendimiento, optimización de costos
¿Listo para construir tu agente de IA? Comienza con SpreadAPI
¿Preguntas? ¿Ejemplos? Escríbenos a hello@airrange.io
P.D. - Cada agente de IA necesita cálculos precisos. Tus modelos de Excel ya los tienen. Es hora de conectar los puntos.
Artículos Relacionados
Explora más guías de API de Excel e integración de IA: