Skip to main content

Instalacion

npm install node-fetch form-data

Cliente Completo

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

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

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

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

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

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

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}`);
    }
  }
}