<?php
namespace App\Controller;
use App\Entity\Car;
use App\Repository\InfoautoTokensRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use App\Entity\InfoautoTokens;
/**
* @Route("/infoauto")
*/
class InfoautoController extends AbstractController
{
#[Route('/', name: 'infoauto_index')]
public function index(): Response
{
$dataJson = json_decode(file_get_contents( $this->getParameter('kernel.project_dir').'/public/brands-groups.json' ), true);
/*---- List brands [BEGIN] ----*/
$aBrands = [];
$aModels = [];
foreach ($dataJson as $brand) {
$aTemp = [];
$aTemp['brandId'] = $brand['id'];
$aTemp['brandName'] = $brand['name'];
$aTemp['brandListPrice'] = $brand['list_price'];
$aTemp['brandPrices'] = $brand['prices'];
$aTemp['brandPriceFrom'] = $brand['prices_from'];
$aTemp['brandPriceTo'] = $brand['prices_to'];
$aTemp['brandGroups'] = $brand['groups'];
/*foreach ($brand['groups'] as $group) {
}*/
$aBrands[] = $aTemp;
}
/*---- List brands [BEGIN] ----*/
//dump($aBrands); exit();
return $this->render('infoauto/index.html.twig', [
'currentMenu' => 'car',
'brands' => $aBrands,
]);
}
#[Route('/getModal', name: 'infoauto_get_modal')]
public function getModal(): Response
{
$dataJson = json_decode(file_get_contents( $this->getParameter('kernel.project_dir').'/public/brands-groups.json' ), true);
/*---- List brands [BEGIN] ----*/
$aBrands = [];
$aModels = [];
foreach ($dataJson as $brand) {
$aTemp = [];
$aTemp['brandId'] = $brand['id'];
$aTemp['brandName'] = $brand['name'];
$aTemp['brandListPrice'] = $brand['list_price'];
$aTemp['brandPrices'] = $brand['prices'];
$aTemp['brandPriceFrom'] = $brand['prices_from'];
$aTemp['brandPriceTo'] = $brand['prices_to'];
$aTemp['brandGroups'] = $brand['groups'];
/*foreach ($brand['groups'] as $group) {
}*/
$aBrands[] = $aTemp;
}
/*---- List brands [BEGIN] ----*/
//dump($aBrands); exit();
return $this->render('car/get_infoauto_modal.html.twig', [
'currentMenu' => 'car',
'brands' => $aBrands,
]);
}
#[Route('/brands/download', name: 'infoauto_brands_download', methods: ['GET'])]
public function downloadBrands(ManagerRegistry $doctrine, InfoautoTokensRepository $infoautoTokens): JsonResponse
{
$token = $this->getInfoautoToken($doctrine, $infoautoTokens);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.infoauto.com.ar/cars/pub/brands/download/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_ENCODING => 'gzip',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer '.$token,
'Accept: application/json',
'Accept-Encoding: gzip',
],
]);
$body = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($body === false || $httpCode >= 400) {
return new JsonResponse([
'status' => 'error',
'message' => 'No se pudo descargar brands-groups.',
'httpCode' => $httpCode,
'curlError' => $curlErr,
'response' => $body ? mb_substr($body, 0, 2000) : null,
], 502);
}
$path = $this->getParameter('kernel.project_dir').'/public/brands-groups.json';
file_put_contents($path, $body);
return new JsonResponse([
'status' => 'success',
'message' => 'brands-groups.json actualizado.',
'path' => $path,
]);
}
/**
* @Route("/getVersions", name="infoauto_get_versions", methods={"GET"})
*/
public function getVersions(ManagerRegistry $doctrine, Request $request, InfoautoTokensRepository $infoautoTokens): JsonResponse
{
$data['brandId'] = $request->get('brandId');
$data['modelId'] = $request->get('modelId');
$data['modelYear'] = $request->get('modelYear');
$token = $this->getInfoautoToken($doctrine, $infoautoTokens);
// Define los encabezados necesarios
$headers = [
'Authorization: Bearer '.$token,
];
$url = 'https://api.infoauto.com.ar/cars/pub//brands/'.$data['brandId'].'/groups/'.$data['modelId'].'/models?&prices=true&prices_from='.$data['modelYear'].'&prices_to='.$data['modelYear'].'&page_size=100';
if ($data['modelYear'] == '0km') {
$url = 'https://api.infoauto.com.ar/cars/pub//brands/'.$data['brandId'].'/groups/'.$data['modelId'].'/models?&list_price=true&page_size=100';
}
/*---- Buscar modelos por año [BEGIN] ----*/
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => $headers, // Encabezados de la petición
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
));
$response = curl_exec($curl);
// Verifica si ocurrió un error de cURL
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
die("Error en cURL: $error");
}
// Obtiene el código HTTP de la respuesta
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$modelsJson = json_decode($response, true);
/*---- Buscar modelos por año [END] ----*/
//dump($modelsJson); exit();
/*---- Buscar precios [BEGIN] ----*/
// Recopilar CODIA
$aCodia = null;
foreach ($modelsJson as $item) {
$aCodia[] = ['name' => $item['description'], 'codia' => $item['codia']];
}
if ($aCodia) {
$rateLimitReached = false;
foreach ($aCodia as $key => $item) {
$priceFloat = null;
// Primero intenta precio en payload /models.
if (isset($modelsJson[$key]) && is_array($modelsJson[$key])) {
$priceFloat = $this->extractInfoautoPrice($modelsJson[$key], $data['modelYear']);
}
// Fallback al endpoint por CODIA (comportamiento del controller anterior).
if (($priceFloat === null || $priceFloat <= 0) && !$rateLimitReached) {
$priceUrl = 'https://api.infoauto.com.ar/cars/pub/models/'.$item['codia'].'/prices';
if ($data['modelYear'] == '0km') {
$priceUrl = 'https://api.infoauto.com.ar/cars/pub/models/'.$item['codia'].'/list_price';
}
$priceCurl = curl_init();
curl_setopt_array($priceCurl, array(
CURLOPT_URL => $priceUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
));
$priceResponse = curl_exec($priceCurl);
$priceHttpCode = (int) curl_getinfo($priceCurl, CURLINFO_HTTP_CODE);
if ($priceResponse === false) {
curl_close($priceCurl);
} else {
curl_close($priceCurl);
$aPrices = json_decode($priceResponse, true);
if ($priceHttpCode === 403 && is_array($aPrices) && (($aPrices['code'] ?? null) == 403)) {
$rateLimitReached = true;
} elseif (is_array($aPrices)) {
if ($data['modelYear'] == '0km') {
foreach ($aPrices as $item2) {
if (is_numeric($item2)) {
$priceFloat = (float) $item2;
break;
}
if (is_array($item2)) {
foreach (['list_price', 'price', 'official_price', 'amount', 'value'] as $priceKey) {
if (isset($item2[$priceKey]) && is_numeric($item2[$priceKey])) {
$priceFloat = (float) $item2[$priceKey];
break 2;
}
}
}
}
} else {
foreach ($aPrices as $item2) {
if (is_array($item2) && isset($item2['year']) && (int) $item2['year'] === (int) $data['modelYear']) {
if (isset($item2['price']) && is_numeric($item2['price'])) {
$priceFloat = (float) $item2['price'];
break;
}
}
}
}
if ($priceFloat === null || $priceFloat <= 0) {
$priceFloat = $this->extractInfoautoPrice($aPrices, $data['modelYear']);
}
}
}
}
$aCodia[$key]['price'] = $priceFloat !== null ? number_format($priceFloat, 0, '', '.') : 0;
}
/*---- Buscar precios [END] ----*/
} else {
$data['status'] = 'error';
$data['type'] = 'version';
$data['message'] = 'No se encontró versión para el modelo';
echo '<h3>'.$data['message'].'</h3>';
exit();
}
//dump($aCodia); exit();
return $this->json($aCodia);
}
#[Route('/getVersionPrice', name: 'infoauto_get_version_price', methods: ['GET'])]
public function getVersionPrice(ManagerRegistry $doctrine, Request $request, InfoautoTokensRepository $infoautoTokens): JsonResponse
{
$codia = $request->get('codia');
$modelYear = (string) $request->get('modelYear');
if (!$codia || $modelYear === '') {
return $this->json([
'status' => 'error',
'message' => 'Parámetros inválidos.',
], 400);
}
$token = $this->getInfoautoToken($doctrine, $infoautoTokens);
$headers = [
'Authorization: Bearer '.$token,
];
$url = 'https://api.infoauto.com.ar/cars/pub/models/'.$codia.'/prices';
if ($modelYear === '0km') {
$url = 'https://api.infoauto.com.ar/cars/pub/models/'.$codia.'/list_price';
}
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
));
$response = curl_exec($curl);
$httpCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
return $this->json([
'status' => 'error',
'message' => 'Error al consultar precio.',
'detail' => $error,
], 502);
}
curl_close($curl);
$pricesPayload = json_decode($response, true);
$priceFloat = $this->extractInfoautoPrice($pricesPayload, $modelYear);
$price = $priceFloat !== null ? number_format($priceFloat, 0, '', '.') : 0;
return $this->json([
'status' => 'success',
'codia' => $codia,
'modelYear' => $modelYear,
'price' => $price,
'httpCode' => $httpCode,
]);
}
/**
* @Route("/getFeatures", name="infoauto_get_features", methods={"GET"})
*/
public function getFeatures(ManagerRegistry $doctrine, Request $request, InfoautoTokensRepository $infoautoTokens): JsonResponse
{
$data['codia'] = $request->get('codia');
$token = $this->getInfoautoToken($doctrine, $infoautoTokens);
// Define los encabezados necesarios
$headers = [
'Authorization: Bearer '.$token,
];
/*---- Buscar modelos por año [BEGIN] ----*/
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.infoauto.com.ar/cars/pub/models/'.$data['codia'].'/features/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
));
$response = curl_exec($curl);
curl_close($curl);
// Verifica si ocurrió un error de cURL
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
die("Error en cURL: $error");
}
// Obtiene el código HTTP de la respuesta
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
//$featuresJson = json_decode($response, true);
$featuresJson = $response;
/*---- Buscar modelos por año [END] ----*/
//dump($featuresJson); exit();
return $this->json($featuresJson);
}
private function getInfoautoToken(ManagerRegistry $doctrine, InfoautoTokensRepository $infoautoTokens)
{
$tokenTmp = $infoautoTokens->findOneBy([], ['id' => 'DESC'], 1);
if ($tokenTmp) {
$hourMore = clone $tokenTmp->getCreatedAt();
$hourMore = ($hourMore)->add(new \DateInterval('PT1H'));
if (new \DateTime() > $hourMore) {
$tokenOk = false;
} else {
$tokenOk = true;
}
} else {
$tokenOk = false;
}
if ($tokenOk) {
return $tokenTmp->getAccessToken();
} else {
$headers = [
'Content-Type: application/json',
'Authorization: Basic '. base64_encode('rwaybar@yahoo.com.ar:NQlv38cCxODlq42f'),
'Content-Length: 0'
];
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.infoauto.com.ar/cars/auth/login',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
));
$response = curl_exec($curl);
curl_close($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
die("Error en cURL: $error");
}
$json = json_decode($response);
$tokens = new InfoautoTokens();
$tokens->setAccessToken($json->access_token);
$tokens->setRefreshToken($json->refresh_token);
$tokens->setCreatedAt(new \DateTime);
$doctrine->getManager()->persist($tokens);
$doctrine->getManager()->flush();
return $json->access_token;
}
}
private function extractInfoautoPrice($payload, string $modelYear): ?float
{
if ($payload === null) {
return null;
}
$targetYear = $modelYear === '0km' ? null : (int) $modelYear;
$queue = [$payload];
$zeroCandidate = null;
while (!empty($queue)) {
$node = array_shift($queue);
if (!is_array($node)) {
$candidate = $this->normalizeNumericValue($node);
if ($targetYear === null && $candidate !== null) {
if ($candidate > 0) {
return $candidate;
}
$zeroCandidate = $zeroCandidate ?? 0.0;
}
continue;
}
if ($targetYear !== null) {
if (isset($node[(string) $targetYear])) {
$candidate = $this->normalizeNumericValue($node[(string) $targetYear]);
if ($candidate !== null) {
if ($candidate > 0) {
return $candidate;
}
$zeroCandidate = $zeroCandidate ?? 0.0;
}
}
if (isset($node[$targetYear])) {
$candidate = $this->normalizeNumericValue($node[$targetYear]);
if ($candidate !== null) {
if ($candidate > 0) {
return $candidate;
}
$zeroCandidate = $zeroCandidate ?? 0.0;
}
}
$lowerNode = array_change_key_case($node, CASE_LOWER);
if (isset($lowerNode['year']) && (int) $lowerNode['year'] === $targetYear) {
foreach (['price', 'list_price', 'value', 'amount', 'official_price', 'used_price'] as $priceKey) {
if (isset($lowerNode[$priceKey])) {
$candidate = $this->normalizeNumericValue($lowerNode[$priceKey]);
if ($candidate !== null) {
if ($candidate > 0) {
return $candidate;
}
$zeroCandidate = $zeroCandidate ?? 0.0;
}
}
}
}
} else {
$lowerNode = array_change_key_case($node, CASE_LOWER);
foreach (['list_price', 'price', 'official_price', 'amount', 'value'] as $priceKey) {
if (isset($lowerNode[$priceKey])) {
$candidate = $this->normalizeNumericValue($lowerNode[$priceKey]);
if ($candidate !== null) {
if ($candidate > 0) {
return $candidate;
}
$zeroCandidate = $zeroCandidate ?? 0.0;
}
}
}
}
foreach ($node as $key => $child) {
if ($targetYear !== null && is_numeric($key) && (int) $key === $targetYear) {
$candidate = $this->normalizeNumericValue($child);
if ($candidate !== null) {
if ($candidate > 0) {
return $candidate;
}
$zeroCandidate = $zeroCandidate ?? 0.0;
}
}
if (is_array($child)) {
$queue[] = $child;
}
}
}
return $zeroCandidate;
}
private function normalizeNumericValue($value): ?float
{
if (is_int($value) || is_float($value)) {
return (float) $value;
}
if (!is_string($value)) {
return null;
}
$clean = trim($value);
if ($clean === '') {
return null;
}
$clean = preg_replace('/[^0-9.,-]/', '', $clean);
if ($clean === null || $clean === '') {
return null;
}
if (preg_match('/^-?\d{1,3}([.,]\d{3})+$/', $clean)) {
$clean = str_replace([',', '.'], '', $clean);
} else {
$clean = str_replace(',', '.', $clean);
}
return is_numeric($clean) ? (float) $clean : null;
}
}