cambios importantes
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
class BotRouter
|
||||
{
|
||||
public static function route(array $company, array $context, string $input, string $inputType = 'text'): void
|
||||
{
|
||||
if (self::shouldSkipBot($company, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = self::classifyAndProcess($company, $context, $input, $inputType);
|
||||
|
||||
if ($response === null) {
|
||||
self::log("Bot no generó respuesta para {$context['from']}");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int)($company['requires_approval'] ?? 0) === 1) {
|
||||
self::saveForApproval($company, $context, $input, $response);
|
||||
return;
|
||||
}
|
||||
|
||||
self::enqueueResponse($response, $company);
|
||||
}
|
||||
|
||||
private static function saveForApproval(array $company, array $context, string $input, array $response): void
|
||||
{
|
||||
$botType = $company['bot_type'] ?? 'normal';
|
||||
|
||||
$result = PendingApproval::create(
|
||||
companyId: (int)$company['id'],
|
||||
phone: $context['from'],
|
||||
name: $context['name'] ?? null,
|
||||
incomingMsg: $input,
|
||||
botResponse: $response,
|
||||
botType: $botType,
|
||||
context: $context,
|
||||
);
|
||||
|
||||
self::log("Respuesta guardada para aprobación #{$result['id']} — empresa {$company['name']}");
|
||||
|
||||
self::notifyErp($company, $result['id']);
|
||||
}
|
||||
|
||||
private static function notifyErp(array $company, int $pendingId): void
|
||||
{
|
||||
$config = self::getConfig($company);
|
||||
$webhookUrl = $config['approval_webhook'] ?? '';
|
||||
|
||||
if ($webhookUrl === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'action' => 'pending',
|
||||
'pending_id' => $pendingId,
|
||||
'company_id' => (int)$company['id'],
|
||||
'message' => 'Nuevo mensaje pendiente de aprobación',
|
||||
]);
|
||||
|
||||
$ch = curl_init($webhookUrl);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'X-API-Key: ' . ($company['api_key'] ?? ''),
|
||||
],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
private static function classifyAndProcess(array $company, array $context, string $input, string $inputType): ?array
|
||||
{
|
||||
$botType = $company['bot_type'] ?? 'normal';
|
||||
|
||||
return match ($botType) {
|
||||
'normal' => self::runNormalBot($company, $context, $input, $inputType),
|
||||
'ai' => self::runAiBot($company, $context, $input),
|
||||
'hybrid' => self::runHybridBot($company, $context, $input, $inputType),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static function runNormalBot(array $company, array $context, string $input, string $inputType): ?array
|
||||
{
|
||||
if ($inputType === 'interactive' || $inputType === 'button') {
|
||||
return NormalBot::processInteractive($company, $context, $input);
|
||||
}
|
||||
return NormalBot::process($company, $context, $input);
|
||||
}
|
||||
|
||||
private static function runAiBot(array $company, array $context, string $input): ?array
|
||||
{
|
||||
return AiBot::process($company, $context, $input);
|
||||
}
|
||||
|
||||
private static function runHybridBot(array $company, array $context, string $input, string $inputType): ?array
|
||||
{
|
||||
$response = self::runNormalBot($company, $context, $input, $inputType);
|
||||
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
self::log("NormalBot no manejó '{$input}', escalando a IA");
|
||||
return self::runAiBot($company, $context, $input);
|
||||
}
|
||||
|
||||
private static function shouldSkipBot(array $company, array $context): bool
|
||||
{
|
||||
if (!(int)($company['is_active'] ?? 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$config = self::getConfig($company);
|
||||
$ignorePrefixes = $config['ignore_prefixes'] ?? [];
|
||||
|
||||
foreach ($ignorePrefixes as $prefix) {
|
||||
if (str_starts_with($context['from'] ?? '', $prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function enqueueResponse(array $response, array $company): void
|
||||
{
|
||||
$to = $response['to'] ?? '';
|
||||
$type = $response['type'] ?? 'text';
|
||||
$payload = $response['payload'] ?? '';
|
||||
|
||||
if ($to === '' || $payload === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO outbound_queue (company_id, to_number, message_type, payload) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$company['id'], $to, $type, $payload]);
|
||||
self::log("Bot encoló respuesta {$type} para {$to}");
|
||||
} catch (\PDOException $e) {
|
||||
self::log('ERROR encolando respuesta: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static function getConfig(array $company): array
|
||||
{
|
||||
$json = $company['config_json'] ?? '';
|
||||
if ($json === '') return [];
|
||||
$config = json_decode($json, true);
|
||||
return is_array($config) ? $config : [];
|
||||
}
|
||||
|
||||
private static function log(string $message): void
|
||||
{
|
||||
$dir = dirname(__DIR__) . '/storage/logs';
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
$file = $dir . '/bot-' . date('Y-m-d') . '.log';
|
||||
$line = '[' . date('Y-m-d H:i:s') . '] [BOT] ' . $message . PHP_EOL;
|
||||
file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
|
||||
|
||||
if (env('APP_ENV', 'production') === 'local') {
|
||||
error_log($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user