Instalacion
Copy
npm install node-fetch form-data
Cliente Completo
Copy
const fs = require('fs');
const FormData = require('form-data');
class NeuracallClient {
constructor(options = {}) {
this.baseUrl = options.baseUrl || process.env.NEURACALL_API_URL || 'https://api.neuracall.com';
this.clientId = options.clientId || process.env.NEURACALL_CLIENT_ID;
this.clientSecret = options.clientSecret || process.env.NEURACALL_CLIENT_SECRET;
this._token = null;
this._expiresAt = 0;
}
async _getToken() {
const now = Date.now() / 1000;
if (now > this._expiresAt - 300) {
const response = await fetch(`${this.baseUrl}/v1/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: this.clientId,
client_secret: this.clientSecret
})
});
if (!response.ok) {
throw new Error(`Auth failed: ${response.status}`);
}
const data = await response.json();
this._token = data.access_token;
this._expiresAt = data.expires_at;
}
return this._token;
}
async _headers() {
return {
'Authorization': `Bearer ${await this._getToken()}`
};
}
// ==================== CALL ANALYSIS ====================
async createAnalysis(audioPath, externalId, agentId, agentName, modelName, additionalParams = null) {
const form = new FormData();
form.append('audio_file', fs.createReadStream(audioPath));
form.append('external_id', externalId);
form.append('agent_id', agentId);
form.append('agent_name', agentName);
form.append('model_name', modelName);
if (additionalParams) {
form.append('additional_parameters', JSON.stringify(additionalParams));
}
const response = await fetch(`${this.baseUrl}/v1/call-analysis`, {
method: 'POST',
headers: {
...await this._headers(),
...form.getHeaders()
},
body: form
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Create analysis failed: ${error.message}`);
}
return response.json();
}
async getAnalysis(analysisId) {
const response = await fetch(
`${this.baseUrl}/v1/call-analysis/${analysisId}`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`Get analysis failed: ${response.status}`);
}
return response.json();
}
async listAnalyses(options = {}) {
const params = new URLSearchParams();
if (options.modelId) params.append('model_id', options.modelId);
if (options.startDate) params.append('start_date', options.startDate);
if (options.endDate) params.append('end_date', options.endDate);
params.append('page', options.page || 0);
params.append('size', options.size || 20);
const response = await fetch(
`${this.baseUrl}/v1/call-analysis?${params}`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`List analyses failed: ${response.status}`);
}
return response.json();
}
async getTranscription(analysisId) {
const response = await fetch(
`${this.baseUrl}/v1/call-analysis/${analysisId}/transcription`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`Get transcription failed: ${response.status}`);
}
return response.json();
}
async waitForCompletion(analysisId, timeout = 600000, pollInterval = 5000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const result = await this.getAnalysis(analysisId);
if (result.status === 'PROCESS_COMPLETED') {
return result;
}
if (result.status === 'ERROR') {
throw new Error(`Analysis failed: ${result.error_message}`);
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
throw new Error(`Analysis ${analysisId} did not complete within ${timeout}ms`);
}
// ==================== MODELS ====================
async listModels(orderByName = false) {
const response = await fetch(
`${this.baseUrl}/v1/models?order_by_name=${orderByName}`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`List models failed: ${response.status}`);
}
return response.json();
}
async getModel(modelId) {
const response = await fetch(
`${this.baseUrl}/v1/models/${modelId}`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`Get model failed: ${response.status}`);
}
return response.json();
}
async createModel(name, description, prompt) {
const response = await fetch(`${this.baseUrl}/v1/models`, {
method: 'POST',
headers: {
...await this._headers(),
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, description, prompt })
});
if (!response.ok) {
throw new Error(`Create model failed: ${response.status}`);
}
return response.json();
}
// ==================== MODEL CATEGORIES ====================
async listCategories(modelId) {
const response = await fetch(
`${this.baseUrl}/v1/models/${modelId}/categories`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`List categories failed: ${response.status}`);
}
return response.json();
}
async createCategory(modelId, name, description, orderNumber) {
const response = await fetch(`${this.baseUrl}/v1/models/${modelId}/categories`, {
method: 'POST',
headers: {
...await this._headers(),
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
description,
order_number: orderNumber
})
});
if (!response.ok) {
throw new Error(`Create category failed: ${response.status}`);
}
return response.json();
}
// ==================== MODEL VARIABLES ====================
async listVariables(modelId) {
const response = await fetch(
`${this.baseUrl}/v1/models/${modelId}/variables`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`List variables failed: ${response.status}`);
}
return response.json();
}
async createVariable(modelId, variableData) {
const response = await fetch(`${this.baseUrl}/v1/models/${modelId}/variables`, {
method: 'POST',
headers: {
...await this._headers(),
'Content-Type': 'application/json'
},
body: JSON.stringify(variableData)
});
if (!response.ok) {
throw new Error(`Create variable failed: ${response.status}`);
}
return response.json();
}
// ==================== MODEL METADATA ====================
async listMetadata(modelId) {
const response = await fetch(
`${this.baseUrl}/v1/models/${modelId}/metadata`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`List metadata failed: ${response.status}`);
}
return response.json();
}
async createMetadata(modelId, key, readableName, type, groupingField = false) {
const response = await fetch(`${this.baseUrl}/v1/models/${modelId}/metadata`, {
method: 'POST',
headers: {
...await this._headers(),
'Content-Type': 'application/json'
},
body: JSON.stringify({
key,
readable_name: readableName,
type,
grouping_field: groupingField
})
});
if (!response.ok) {
throw new Error(`Create metadata failed: ${response.status}`);
}
return response.json();
}
// ==================== STATISTICS ====================
async getBasicStats(startDate, endDate, modelName) {
const params = new URLSearchParams({
start_date: startDate,
end_date: endDate,
model_name: modelName
});
const response = await fetch(
`${this.baseUrl}/v1/stats/basic?${params}`,
{ headers: await this._headers() }
);
if (!response.ok) {
throw new Error(`Get stats failed: ${response.status}`);
}
return response.json();
}
}
module.exports = NeuracallClient;
Ejemplo de Uso
Copy
const NeuracallClient = require('./neuracall-client');
async function main() {
const client = new NeuracallClient();
try {
// Listar modelos
console.log('Modelos disponibles:');
const models = await client.listModels();
models.forEach(model => {
console.log(` - ${model.name} (ID: ${model.id})`);
});
// Crear análisis
console.log('\nCreando análisis...');
const analysis = await client.createAnalysis(
'llamada.mp3',
'CALL-2024-001',
'AGT-001',
'Juan Perez',
models[0].name
);
console.log(`Análisis creado: ${analysis.id}`);
console.log(`Estado: ${analysis.status}`);
// Esperar resultado
console.log('\nEsperando resultado...');
const result = await client.waitForCompletion(analysis.id);
// Mostrar resultados
console.log('\n=== RESULTADO ===');
console.log(`NeuraScore: ${result.score_percentage}%`);
console.log(`Resumen: ${result.summary}`);
if (result.insights) {
console.log('\nInsights:');
result.insights.forEach((insight, i) => {
console.log(` ${i + 1}. ${insight}`);
});
}
if (result.keywords) {
console.log(`\nKeywords: ${result.keywords.join(', ')}`);
}
} catch (error) {
console.error('Error:', error.message);
}
}
main();
Con Async/Await
Copy
const NeuracallClient = require('./neuracall-client');
const client = new NeuracallClient();
// Función helper para analizar llamadas
async function analyzeCall(audioPath, callInfo) {
const analysis = await client.createAnalysis(
audioPath,
callInfo.externalId,
callInfo.agentId,
callInfo.agentName,
callInfo.modelName
);
return client.waitForCompletion(analysis.id);
}
// Uso
const result = await analyzeCall('llamada.mp3', {
externalId: 'CALL-001',
agentId: 'AGT-001',
agentName: 'Juan Perez',
modelName: 'Evaluación Servicio'
});
console.log(`Score: ${result.score_percentage}%`);
Manejo de Errores
Copy
try {
const result = await client.createAnalysis(...);
} catch (error) {
if (error.message.includes('401')) {
console.log('Error de autenticación');
} else if (error.message.includes('400')) {
console.log('Datos invalidos');
} else {
console.log(`Error: ${error.message}`);
}
}
Gestión de Modelos
Crear un Modelo Completo
Copy
const NeuracallClient = require('./neuracall-client');
async function setupCompleteModel() {
const client = new NeuracallClient();
// 1. Crear el modelo base
const model = await client.createModel(
'Evaluación Ventas Q1',
'Modelo para evaluar llamadas de ventas',
'Evalúa la llamada considerando: saludo, identificación de necesidades, presentación y cierre.'
);
const modelId = model.id;
console.log(`Modelo creado: ${model.name} (ID: ${modelId})`);
// 2. Crear categorías
const categoriesData = [
{ name: 'Apertura', description: 'Saludo e identificación', orderNumber: 1 },
{ name: 'Descubrimiento', description: 'Identificación de necesidades', orderNumber: 2 },
{ name: 'Presentación', description: 'Exposición de la solución', orderNumber: 3 },
{ name: 'Cierre', description: 'Confirmación y siguientes pasos', orderNumber: 4 }
];
const categories = {};
for (const catData of categoriesData) {
const cat = await client.createCategory(
modelId,
catData.name,
catData.description,
catData.orderNumber
);
categories[cat.name] = cat.id;
console.log(` Categoría creada: ${cat.name} (ID: ${cat.id})`);
}
// 3. Crear variables
const variablesData = [
// Apertura (20 puntos)
{
key: 'greeting', readable_name: 'Saludo',
description: 'Saludo cordial y profesional',
type: 'INTEGER', weight: 10, category: 'Apertura', required: true
},
{
key: 'identification', readable_name: 'Identificación',
description: 'Se identificó correctamente',
type: 'INTEGER', weight: 10, category: 'Apertura', required: true
},
// Descubrimiento (25 puntos)
{
key: 'open_questions', readable_name: 'Preguntas Abiertas',
description: 'Usó preguntas abiertas',
type: 'INTEGER', weight: 15, category: 'Descubrimiento', required: true
},
{
key: 'active_listening', readable_name: 'Escucha Activa',
description: 'Demostró escucha activa',
type: 'INTEGER', weight: 10, category: 'Descubrimiento', required: true
},
// Presentación (30 puntos)
{
key: 'benefits', readable_name: 'Presentación de Beneficios',
description: 'Presentó beneficios claramente',
type: 'INTEGER', weight: 15, category: 'Presentación', required: true
},
{
key: 'objections', readable_name: 'Manejo de Objeciones',
description: 'Manejó objeciones efectivamente',
type: 'INTEGER', weight: 15, category: 'Presentación', required: true
},
// Cierre (25 puntos)
{
key: 'proposal', readable_name: 'Propuesta Clara',
description: 'Hizo una propuesta clara',
type: 'INTEGER', weight: 10, category: 'Cierre', required: true
},
{
key: 'next_steps', readable_name: 'Próximos Pasos',
description: 'Confirmó los próximos pasos',
type: 'INTEGER', weight: 15, category: 'Cierre', required: true
},
// Variable de extracción (sin peso)
{
key: 'product_interest', readable_name: 'Producto de Interés',
description: 'Producto mencionado',
type: 'STRING', weight: 0, category: 'Descubrimiento', required: false
}
];
for (const varData of variablesData) {
const variable = await client.createVariable(modelId, {
key: varData.key,
readable_name: varData.readable_name,
description: varData.description,
type: varData.type,
weight: varData.weight,
category_id: categories[varData.category],
required: varData.required
});
console.log(` Variable creada: ${variable.readable_name} (peso: ${variable.weight})`);
}
// 4. Crear campos de metadata
const metadataFields = [
{ key: 'campaign', readableName: 'Campaña', type: 'STRING', groupingField: true },
{ key: 'product', readableName: 'Producto', type: 'STRING', groupingField: true },
{ key: 'region', readableName: 'Región', type: 'STRING', groupingField: true }
];
for (const metaData of metadataFields) {
const meta = await client.createMetadata(
modelId,
metaData.key,
metaData.readableName,
metaData.type,
metaData.groupingField
);
console.log(` Metadata creada: ${meta.readable_name} (agrupación: ${meta.grouping_field})`);
}
console.log(`\nModelo '${model.name}' configurado completamente!`);
return model;
}
setupCompleteModel().catch(console.error);
Listar Configuración de un Modelo
Copy
async function showModelConfiguration(modelId) {
const client = new NeuracallClient();
const model = await client.getModel(modelId);
console.log(`Modelo: ${model.name}`);
const categories = await client.listCategories(modelId);
console.log(`\nCategorías (${categories.length}):`);
categories.forEach(cat => {
console.log(` - ${cat.name} (orden: ${cat.order_number})`);
});
const variables = await client.listVariables(modelId);
console.log(`\nVariables (${variables.length}):`);
let totalWeight = 0;
variables.forEach(variable => {
console.log(` - ${variable.readable_name}: ${variable.type} (peso: ${variable.weight})`);
totalWeight += variable.weight;
});
console.log(` Total de pesos: ${totalWeight}`);
const metadata = await client.listMetadata(modelId);
console.log(`\nMetadata (${metadata.length}):`);
metadata.forEach(meta => {
const grouping = meta.grouping_field ? '✓' : '✗';
console.log(` - ${meta.readable_name}: ${meta.type} (agrupación: ${grouping})`);
});
}
showModelConfiguration(15).catch(console.error);
Manejo de Errores en Modelos
Copy
async function handleModelErrors() {
const client = new NeuracallClient();
try {
// Intentar modificar un modelo con análisis existentes
await client.createVariable(15, {
key: 'new_variable',
readable_name: 'Nueva Variable',
description: '...',
type: 'INTEGER',
weight: 10,
category_id: 42,
required: true
});
} catch (error) {
if (error.message.includes('409')) {
console.log('Error: No se puede modificar un modelo que ya tiene análisis asociados.');
console.log('Solución: Crea una nueva versión del modelo.');
} else if (error.message.includes('404')) {
console.log('Error: El modelo o categoría no existe.');
} else {
console.log(`Error: ${error.message}`);
}
}
}
