Plugin Directory

Changeset 3783588


Ignore:
Timestamp:
11/04/2026 09:54:16 AM (15 hours ago)
Author:
facturaone
Message:

v5.54

Location:
wp-facturaone/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • wp-facturaone/trunk/FONE_imagenes.php

    r3608362 r3783588  
    11<?php
     2/* INDICE DE CAMBIOS GPT
     3 * GPT0000000001 - Al retirar variaciones, conservar adjuntos usados por el principal u otros productos.
     4 */
    25if ( ! defined( 'ABSPATH' ) ) exit;
    36
    … …  
    856859
    857860            if ($img_id > 0) {
    858                 wp_delete_attachment($img_id, true);
     861                // GPT0000000001 - La imagen de una variacion puede ser tambien la del principal o su galeria.
     862                FONE_delete_attachment_safe($img_id, $variation_id);
    859863            }
    860864
  • wp-facturaone/trunk/FONE_index.php

    r3764464 r3783588  
    99 * GPT0000000006 - 2026-10-25: configurar en Avanzado el envio de pedidos con importe cero, desactivado por defecto.
    1010 * GPT0000000007 - 2026-10-25: mover Pedidos con importe 0 al final, despues de Personalizar Texto Reserva.
     11 * GPT0000000008 - Identificar los productos con error y abrir su detalle desde Mas informacion.
    1112 */
    1213?>
    … …  
    415416    function FONE_importPopup(options) {
    416417      var popup = window.Swal || window.swal;
    417       if (popup && typeof popup.fire === 'function') popup.fire(options);
    418     }
     418      // GPT0000000008 - Permitir el detalle en la propia pagina si el dialogo no esta disponible.
     419      if (popup && typeof popup.fire === 'function') {
     420        try { popup.fire(options); return true; } catch (e) {}
     421      }
     422      return false;
     423    }
     424    // GPT0000000008 - Mostrar solo los campos identificativos y el error que devuelve el servidor.
     425    window.FONE_import_issue_details = '';
     426    function FONE_importErrorDetails(diagnostic) {
     427      var rows = Array.isArray(diagnostic.error_rows) ? diagnostic.error_rows : [];
     428      if (!rows.length) return '';
     429      var html = '<div style="text-align:left;overflow-wrap:anywhere;line-height:1.5;">';
     430      if (diagnostic.code === 'worker_item_errors') html += '<p>La importación sigue en curso y continúa con los demás elementos.</p>';
     431      if (diagnostic.code === 'worker_finished_with_errors') html += '<p>El proceso ha terminado con errores en los elementos indicados.</p>';
     432      for (var i=0; i<rows.length && i<20; i++) {
     433        var row = rows[i] || {}, product = row.product || {}, exception = row.exception || {};
     434        html += '<div style="border:1px solid #ddd;border-radius:5px;padding:10px 12px;margin:10px 0;">'
     435             + '<p style="margin:0 0 8px;"><strong>Elemento ' + FONE_escapeHtml(String(row.seq || '')) + ' (' + FONE_escapeHtml(row.func || '') + ')</strong></p>';
     436        function detail(label, value) {
     437          if (value !== undefined && value !== null && value !== '') html += '<div><strong>' + label + ':</strong> <span style="white-space:pre-wrap;">' + FONE_escapeHtml(String(value)) + '</span></div>';
     438        }
     439        detail('Nombre', product.name);
     440        detail('SKU / referencia', product.sku);
     441        detail('ID en FacturaONE', product.erp_id);
     442        detail('ID en WooCommerce', product.woo_id);
     443        detail('ID padre', product.parent_id);
     444        if (!product.name && !product.sku && !product.erp_id && !product.woo_id && !product.parent_id) html += '<p>No se guardó la identificación del producto en este registro.</p>';
     445        detail('Tarea', row.task_file);
     446        detail('Motivo', row.error);
     447        detail('Tipo de error', exception.type);
     448        detail('Archivo', exception.file);
     449        detail('Línea', exception.line);
     450        html += '</div>';
     451      }
     452      if (parseInt(diagnostic.errors,10) > Math.min(rows.length,20)) html += '<p>Se muestran los primeros ' + Math.min(rows.length,20) + ' de ' + FONE_escapeHtml(String(diagnostic.errors)) + ' errores registrados.</p>';
     453      return html + '</div>';
     454    }
     455    function FONE_openImportDetails() {
     456      if (!window.FONE_import_issue_details) return;
     457      var html = '<div style="max-height:65vh;overflow:auto;">' + window.FONE_import_issue_details + '</div>';
     458      if (FONE_importPopup({title:'Detalle de los productos con error', html:html, width:800})) return;
     459      var box = document.getElementById('FONE-import-diagnostic');
     460      if (!box) return;
     461      var detail = document.getElementById('FONE-import-error-details');
     462      if (!detail) {
     463        detail = document.createElement('div');
     464        detail.id = 'FONE-import-error-details';
     465        box.appendChild(detail);
     466      }
     467      detail.innerHTML = html;
     468      detail.style.display = 'block';
     469    }
     470    document.addEventListener('click', function(e) {
     471      var button = e.target;
     472      while (button && button !== document && !(button.getAttribute && button.getAttribute('data-fone-import-action') === 'details')) button = button.parentNode;
     473      if (!button || button === document) return;
     474      var scope = button.parentNode;
     475      while (scope && scope !== document && !(scope.getAttribute && scope.getAttribute('data-fone-import-details-root') === '1')) scope = scope.parentNode;
     476      if (!scope || scope === document) return;
     477      e.preventDefault();
     478      FONE_openImportDetails();
     479    });
    419480    function FONE_showImportIssue(info, key) {
    420481      var box = document.getElementById('FONE-import-diagnostic');
    … …  
    427488        target.insertBefore(box, target.firstChild);
    428489      }
     490      // GPT0000000008 - Actualizar el detalle incluso cuando el aviso ya se notifico durante el progreso.
     491      window.FONE_import_issue_details = info.details_html || '';
     492      var content = info.html;
     493      if (window.FONE_import_issue_details) content += '<p data-fone-import-details-root="1"><button type="button" class="button" data-fone-import-action="details">Más información</button></p>';
    429494      box.style.display = 'block';
    430       box.innerHTML = '<p><strong>' + FONE_escapeHtml(info.title) + '</strong></p>' + info.html;
     495      box.innerHTML = '<p><strong>' + FONE_escapeHtml(info.title) + '</strong></p>' + content;
    431496      var watch = window.FONE_import_watch;
    432497      if (watch && key && watch.reported[key]) return;
    433498      if (watch && key) watch.reported[key] = true;
    434       FONE_importPopup({type:'error', title:info.title, html:info.html, width:760});
     499      FONE_importPopup({type:'error', title:info.title, html:content, width:760});
    435500    }
    436501    function FONE_clearImportIssue() {
     502      // GPT0000000008 - No reutilizar el detalle de una importacion anterior.
     503      window.FONE_import_issue_details = '';
    437504      var box = document.getElementById('FONE-import-diagnostic');
    438505      if (box) box.style.display = 'none';
    … …  
    572639      field('Pendientes', diagnostic.pending);
    573640      field('Errores', diagnostic.errors || info.queue_errors);
    574       var rows = diagnostic.error_rows || [];
     641      // GPT0000000008 - Identificacion breve en el aviso y detalle de todos los errores disponibles.
     642      var rows = Array.isArray(diagnostic.error_rows) ? diagnostic.error_rows : [];
    575643      for (var i=0; i<rows.length && i<5; i++) {
     644        var product = rows[i].product || {}, identity = [];
     645        if (product.name) identity.push(String(product.name));
     646        if (product.sku) identity.push('SKU: ' + String(product.sku));
     647        if (product.erp_id) identity.push('ID FacturaONE: ' + String(product.erp_id));
     648        if (product.woo_id) identity.push('ID WooCommerce: ' + String(product.woo_id));
     649        field('Producto del elemento ' + rows[i].seq, identity.join(' | '));
    576650        field('Elemento ' + rows[i].seq + ' (' + rows[i].func + ')', rows[i].error);
    577651      }
    578652      var html = '<pre style="white-space:pre-wrap;overflow-wrap:anywhere;text-align:left;font-size:12px;max-height:340px;overflow:auto;">' + FONE_escapeHtml(lines.join('\n').substring(0,6000)) + '</pre>';
    579653      if (raw) html += '<details><summary>Respuesta recibida de WordPress</summary><pre style="white-space:pre-wrap;overflow-wrap:anywhere;text-align:left;font-size:12px;max-height:200px;overflow:auto;">' + FONE_escapeHtml(raw.substring(0,2000)) + '</pre></details>';
    580       return {title: stage === 'Consulta de progreso' ? 'Error al consultar el progreso' : 'Error al importar productos', html:html};
     654      return {title: stage === 'Consulta de progreso' ? 'Error al consultar el progreso' : 'Error al importar productos', html:html, details_html:FONE_importErrorDetails(diagnostic)};
    581655    }
    582656    function execbackground(phpfile,accion,e){
  • wp-facturaone/trunk/ajax.php

    r3758931 r3783588  
    33 * GPT0000000001 - Exponer errores y falta de avance del proceso al consultar su progreso.
    44 * GPT0000000002 - Consultar el resumen final sin cache antes de informar de una cola vacia.
     5 * GPT0000000003 - Conservar producto y excepcion en los errores y mostrar hasta veinte detalles al finalizar.
    56 */
    67if ( ! defined('ABSPATH') ) {
    … …  
    3334}}
    3435
     36// GPT0000000003 - Contexto de la fila actual; no se guarda el payload ni datos de conexion.
     37if (!function_exists('FONE_ajax_product_context')) {
     38function FONE_ajax_product_context($product) {
     39    if (!isset($GLOBALS['FONE_import_error_context']) || !is_array($product)) { return; }
     40    foreach (array('erp_id', 'name', 'sku', 'woo_id', 'parent_id') as $key) {
     41        if (isset($product[$key]) && is_scalar($product[$key])) {
     42            $GLOBALS['FONE_import_error_context'][$key] = $product[$key];
     43        }
     44    }
     45}}
     46if (!function_exists('FONE_ajax_diagnostic_text')) {
     47function FONE_ajax_diagnostic_text($text, $limit = 2000) {
     48    if (!is_scalar($text)) { return ''; }
     49    $text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', (string)$text);
     50    $secret = (string)get_option('FacturaONE_APIKEY', '');
     51    if ($secret !== '') { $text = str_replace($secret, '[API Key oculta]', $text); }
     52    return preg_match('/^.{0,'.(int)$limit.'}/us', $text, $match) ? $match[0] : '[Texto no valido en UTF-8]';
     53}}
     54if (!function_exists('FONE_ajax_error_detail')) {
     55function FONE_ajax_error_detail($error, $product = array(), $exception = array()) {
     56    $detail = array('version'=>1, 'error'=>FONE_ajax_diagnostic_text($error), 'product'=>array(), 'exception'=>array());
     57    foreach (array('erp_id'=>64, 'name'=>300, 'sku'=>150, 'woo_id'=>64, 'parent_id'=>64) as $key=>$limit) {
     58        if (isset($product[$key])) { $detail['product'][$key] = FONE_ajax_diagnostic_text($product[$key], $limit); }
     59    }
     60    foreach (array('type'=>150, 'file'=>180, 'line'=>12) as $key=>$limit) {
     61        if (isset($exception[$key])) { $detail['exception'][$key] = FONE_ajax_diagnostic_text($exception[$key], $limit); }
     62    }
     63    return $detail;
     64}}
     65if (!function_exists('FONE_ajax_error_rows')) {
     66function FONE_ajax_error_rows($job_id) {
     67    global $wpdb;
     68    $table = $wpdb->prefix . 'fone_jobs';
     69    $rows = $wpdb->get_results($wpdb->prepare("SELECT seq, func, task_file, LEFT(error, 16000) AS error FROM $table WHERE job_id=%d AND status=3 ORDER BY seq ASC LIMIT 20", (int)$job_id), ARRAY_A);
     70    if (!is_array($rows)) { return array(); }
     71    foreach ($rows as &$row) {
     72        $stored = (string)($row['error'] ?? '');
     73        $detail = null;
     74        if (strpos($stored, 'FONE_IMPORT_DETAIL:') === 0) {
     75            $parsed = json_decode(substr($stored, strlen('FONE_IMPORT_DETAIL:')), true);
     76            if (is_array($parsed) && ($parsed['version'] ?? 0) === 1 && isset($parsed['error'])) {
     77                $detail = FONE_ajax_error_detail($parsed['error'], (array)($parsed['product'] ?? array()), (array)($parsed['exception'] ?? array()));
     78            }
     79        }
     80        if ($detail === null) { $detail = FONE_ajax_error_detail($stored); }
     81        $row['error'] = $detail['error'];
     82        $row['product'] = $detail['product'];
     83        $row['exception'] = $detail['exception'];
     84        $row['task_file'] = basename((string)($row['task_file'] ?? ''));
     85    }
     86    unset($row);
     87    return $rows;
     88}}
     89
    3590if (!function_exists('FONE_ajax_finish_job')) {
    3691function FONE_ajax_finish_job($job_id, $backgroundtask = '') {
    … …  
    56111
    57112        if ($summary['errors'] > 0) {
    58             $summary['error_rows'] = $wpdb->get_results(
    59                 $wpdb->prepare("SELECT seq, func, LEFT(error, 250) AS error FROM $table WHERE job_id=%d AND status=3 ORDER BY seq ASC LIMIT 20", $job_id),
    60                 ARRAY_A
    61             );
    62         }
    63     }
    64 
    65     update_option('_facturaone_job_last_summary', wp_json_encode($summary));
     113            // GPT0000000003 - Conservar identificadores antes de limpiar la cola.
     114            $summary['error_rows'] = FONE_ajax_error_rows($job_id);
     115        }
     116    }
     117
     118    // GPT0000000003 - Cargar el informe detallado solo cuando se consulte, no en cada visita.
     119    update_option('_facturaone_job_last_summary', wp_json_encode($summary), false);
    66120    update_option('_facturaone_status_backgroundid', '0');
    67121    update_option('_facturaone_status_realizado', '');
    … …  
    191245        if ($errors > 0) {
    192246            $import_diagnostic = array('code'=>'worker_finished_with_errors', 'stage'=>'worker', 'job_id'=>$summary_job,
    193                 'msg'=>'La importacion ha terminado con errores.', 'errors'=>$errors, 'error_rows'=>array_slice((array)($summary['error_rows'] ?? array()), 0, 5));
     247                // GPT0000000003 - Resumen completo para el boton Mas informacion.
     248                'msg'=>'La importacion ha terminado con errores.', 'tasks'=>$total, 'done_or_error'=>$done_or_error, 'pending'=>0, 'errors'=>$errors, 'error_rows'=>array_slice((array)($summary['error_rows'] ?? array()), 0, 20));
    194249        }
    195250    } elseif ($status_backgroundid > 0 && (!$requested_job || $requested_job === $status_backgroundid)) {
    196251        $dispatch_error = (string)get_option('_facturaone_job_dispatch_error_'.$status_backgroundid, '');
    197252        if ($errors > 0) {
    198             $error_rows = $wpdb->get_results($wpdb->prepare("SELECT seq, func, LEFT(error, 250) AS error FROM $table WHERE job_id=%d AND status=3 ORDER BY seq ASC LIMIT 5", $status_backgroundid), ARRAY_A);
     253            // GPT0000000003 - Mismo detalle durante el proceso y tras finalizar.
     254            $error_rows = FONE_ajax_error_rows($status_backgroundid);
    199255            $import_diagnostic = array('code'=>'worker_item_errors', 'msg'=>'El proceso ha registrado errores al importar elementos.', 'error_rows'=>$error_rows);
    200256        } elseif ($dispatch_error !== '') {
    … …  
    405461        $ok = false;
    406462        $err = null;
     463        // GPT0000000003 - Reiniciar por fila para no atribuir un error al producto anterior.
     464        $GLOBALS['FONE_import_error_context'] = array();
     465        $error_exception = array();
    407466
    408467        try {
    … …  
    434493        } catch (Throwable $e) {
    435494            $err = $e->getMessage();
     495            // GPT0000000003 - Solo clase, archivo y linea; nunca argumentos ni traza completa.
     496            $error_exception = array('type'=>get_class($e), 'file'=>basename($e->getFile()), 'line'=>$e->getLine());
    436497        } catch (Exception $e) {
    437498            $err = $e->getMessage();
     499            $error_exception = array('type'=>get_class($e), 'file'=>basename($e->getFile()), 'line'=>$e->getLine());
    438500        }
    439501
    … …  
    448510            }
    449511        } else {
    450             $wpdb->update($table, array('status' => 3, 'error' => $err), array('id' => (int) $r['id']), array('%d', '%s'), array('%d'));
     512            // GPT0000000003 - El fallo afecta a esta fila; el foreach sigue con las siguientes.
     513            $detail = FONE_ajax_error_detail($err, $GLOBALS['FONE_import_error_context'], $error_exception);
     514            $encoded_error = wp_json_encode($detail, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
     515            $row_error = $encoded_error !== false ? 'FONE_IMPORT_DETAIL:'.$encoded_error : FONE_ajax_diagnostic_text($err);
     516            $wpdb->update($table, array('status' => 3, 'error' => $row_error), array('id' => (int) $r['id']), array('%d', '%s'), array('%d'));
    451517            error_log('FONE job row error job_id=' . $job_id . ' seq=' . (int) $r['seq'] . ' func=' . (string) $r['func'] . ' error=' . (string) $err);
    452518        }
    453519
     520        // GPT0000000003 - El contexto solo existe durante la ejecucion de cada fila.
     521        unset($GLOBALS['FONE_import_error_context']);
    454522        update_option('_facturaone_job_last_run_' . $job_id, time());
    455523
  • wp-facturaone/trunk/readme.txt

    r3764464 r3783588  
    66WC requires at least: 8.0
    77WC tested up to: 10.0
    8 Stable tag: 5.53
     8Stable tag: 5.54
    99License: GPLv3
    1010License URI: https://www.gnu.org/licenses/gpl-3.0.html
  • wp-facturaone/trunk/wp-facturaone.php

    r3764464 r3783588  
    44Plugin URI: https://www.facturaone.com/
    55Description: La forma más fácil de trabajar con tu tienda online desde nuestro ERP FacturaONE.
    6 Version: 5.53
     6Version: 5.54
    77Author: FacturaONE
    88Author URI: https://www.facturaone.com/
    … …  
    2020 * Indice de cambios GPT:
    2121 * GPT0000000001 - 2026-10-25: opcion para omitir pedidos de importe cero por defecto en envios y emision.
     22 * GPT0000000002 - Identificar el producto o variacion de los errores de importacion sin alterar su guardado.
     23 * GPT0000000003 - Recuperar como simple un principal ERP asociado a una variacion sin padre, conservando su ID.
     24 * GPT0000000004 - Sincronizar el tipo simple, variable o compuesto segun ERP antes de guardar y renovar su cache.
    2225 */
    2326if ( ! defined( 'ABSPATH' ) ) exit;
    … …  
    545548}
    546549
     550// GPT0000000004 - Usar la clase del tipo ERP, aunque WooCommerce conserve otro tipo anterior.
     551function FONE_new_product_for_erp($product_id, $product_type) {
     552    $product_id = (int)$product_id;
     553    $classname = WC_Product_Factory::get_product_classname($product_id, $product_type);
     554    $product = new $classname($product_id);
     555    if (!($product instanceof WC_Product) || !$product->is_type($product_type)) {
     556        throw new Exception('WooCommerce no dispone de una clase compatible con el tipo ERP '
     557            .$product_type.' para el producto '.$product_id.'. Revisar la extension de productos correspondiente.');
     558    }
     559    return $product;
     560}
     561
     562// GPT0000000004 - Evitar que una lectura posterior (stock/SEO) reutilice el tipo anterior.
     563function FONE_set_erp_product_type($product_id, $product_type) {
     564    $product_id = (int)$product_id;
     565    $result = wp_set_object_terms($product_id, $product_type, 'product_type', false);
     566    if (is_wp_error($result)) {
     567        throw new Exception('No se ha podido establecer el tipo '.$product_type.' del producto WooCommerce '
     568            .$product_id.': '.$result->get_error_message());
     569    }
     570    clean_post_cache($product_id);
     571    if (class_exists('WC_Cache_Helper')) {
     572        WC_Cache_Helper::invalidate_cache_group('product_'.$product_id);
     573    }
     574    wp_cache_delete('product_type_'.$product_id, 'products');
     575    wp_cache_delete($product_id, 'products');
     576    wc_delete_product_transients($product_id);
     577}
     578
    547579function FONE_subeproducto($baseurlfact, $deletevariaciones, $item_lookup_id, $item_name, $item_description, $item_description_web, $urlimage, $item_price, $tarifa1, $tarifa2, $tarifa3, $tarifa4, $tarifa5, $item_kgs_neto, $item_kgs_bruto, $familia_id, $subfamilia_id, $item_activado, $tax_rate_id, $variaciones, $compuestos, $item_txt_alternativo, $marca, $ean, $item_clientid, $item_onorder, $item_wp_stock_control, $item_wp_stock_visible, $item_seo_fraseobjetivo, $item_seo_metadescripcion, $item_wp_visibilidad, $item_wp_destacado, $item_slug, $item_description_web_corta, $item_wp_backorders, $item_categorias, $item_sku, $familia_web_activate, $marca_web_activate, $largo='', $ancho='', $alto='', $item_minquantity='', $item_maxquantity='', $item_stepquantity='', $item_stock=0){
    548580
     581    // GPT0000000002 - Identificar el articulo incluso si falla la busqueda/carga en WooCommerce.
     582    if (function_exists('FONE_ajax_product_context')) {
     583        FONE_ajax_product_context(array('erp_id'=>$item_lookup_id, 'name'=>$item_name, 'sku'=>$item_sku, 'woo_id'=>'', 'parent_id'=>''));
     584    }
    549585    static $cfg = null;
    550586    static $tax_cache = [];
    … …  
    564600
    565601    $_product_id  = (int) fone_get_product_id_by_idfactura($item_lookup_id, $item_sku);
     602    // GPT0000000002 - Incluir el ID que WooCommerce intenta cargar.
     603    if (function_exists('FONE_ajax_product_context')) { FONE_ajax_product_context(array('woo_id'=>$_product_id)); }
    566604    $itemnameorig = $item_name;
    567605
    … …  
    578616    if (trim((string)$item_name) == '') { $item_name = $itemnameorig; }
    579617
     618    // GPT0000000004 - El tipo actual del ERP manda en altas y actualizaciones de cualquier principal.
     619    $hasVar = FONE_has_data($variaciones);
     620    $hasCom = FONE_has_data($compuestos);
     621    $fone_product_type = $hasVar ? 'variable' : ($hasCom ? 'composite' : 'simple');
     622
    580623    // ---------------------------
    581624    // UPDATE producto existente
    … …  
    583626    if ($_product_id > 0) {
    584627
    585         //comprueba si es simple, variable o compuesto
    586         $hasVar = FONE_has_data($variaciones);
    587         $hasCom = FONE_has_data($compuestos);
    588         if (!$hasVar && !$hasCom) {
    589             $objProduct = new WC_Product_Simple((int)$_product_id);
    590         } else {
    591             $objProduct = wc_get_product((int)$_product_id); // variable o composite
    592         }       
    593        
    594         if (!$objProduct) { return 0; }
     628        // GPT0000000003 - Solo recuperar una variacion huerfana vinculada por su ID ERP exacto.
     629        // FONE_subeproducto recibe principales; una variacion con padre exige revisar la asociacion.
     630        if (get_post_type($_product_id) === 'product_variation') {
     631            $fone_parent_id = (int) wp_get_post_parent_id($_product_id);
     632            $fone_linked_erp_id = get_post_meta($_product_id, 'fone_idfactura', true);
     633            $fone_exact_erp_link = is_scalar($fone_linked_erp_id)
     634                && trim((string)$fone_linked_erp_id) === trim((string)$item_lookup_id)
     635                && trim((string)$item_lookup_id) !== '';
     636            if (function_exists('FONE_ajax_product_context')) {
     637                FONE_ajax_product_context(array('parent_id'=>$fone_parent_id));
     638            }
     639            if ($fone_parent_id !== 0 || $hasVar || $hasCom || !$fone_exact_erp_link) {
     640                throw new Exception('El articulo principal ERP '.$item_lookup_id.' esta asociado a la variacion WooCommerce '
     641                    .$_product_id.' (padre: '.$fone_parent_id.'). Revisar la asociacion; no se ha convertido a producto simple.');
     642            }
     643
     644            // Incluir el nombre ERP permite recuperar tambien el registro con titulo vacio.
     645            $fone_recovered_id = wp_update_post(wp_slash(array(
     646                'ID'          => $_product_id,
     647                'post_type'   => 'product',
     648                'post_parent' => 0,
     649                'post_title'  => (string)$item_name,
     650            )), true);
     651            if (is_wp_error($fone_recovered_id) || (int)$fone_recovered_id !== $_product_id) {
     652                $fone_recovery_error = is_wp_error($fone_recovered_id)
     653                    ? $fone_recovered_id->get_error_message() : 'WordPress no ha confirmado el ID actualizado.';
     654                throw new Exception('No se ha podido recuperar el producto WooCommerce '.$_product_id.': '.$fone_recovery_error);
     655            }
     656            FONE_set_erp_product_type($_product_id, 'simple');
     657        }
     658
     659        // GPT0000000004 - Guardar directamente con la clase de destino; no reutilizar la anterior.
     660        $objProduct = FONE_new_product_for_erp($_product_id, $fone_product_type);
    595661
    596662        // Precio (comparación mínima)
    … …  
    801867    // CREATE nuevo producto
    802868    // ---------------------------
    803     $objProduct = new WC_Product_Simple();
     869    // GPT0000000004 - Aplicar la misma seleccion de tipo al crear.
     870    $objProduct = FONE_new_product_for_erp(0, $fone_product_type);
    804871    $objProduct->set_name($item_name);
    805872
    … …  
    9411008    $product_id = $objProduct->save();
    9421009    $_product_id = (int) $product_id;
     1010    // GPT0000000002 - Incluir tambien el ID de un producto recien creado.
     1011    if (function_exists('FONE_ajax_product_context')) { FONE_ajax_product_context(array('woo_id'=>$_product_id)); }
    9431012
    9441013    // slug old
    … …  
    10891158
    10901159    if ($hasVar){
    1091         // 1) Cambiar taxonomy product_type
    1092         wp_remove_object_terms($_product_id, 'simple', 'product_type');
    1093         if (get_term_by('slug','composite','product_type')) {
    1094             wp_remove_object_terms($_product_id, 'composite', 'product_type');
    1095         }
    1096         wp_set_object_terms($_product_id, 'variable', 'product_type', false);
    1097 
    1098         // 2) Limpia caches ANTES de tocar el objeto
    1099         clean_post_cache($_product_id);
    1100         wc_delete_product_transients($_product_id);
    1101         wp_cache_delete($_product_id, 'products');
     1160        // GPT0000000004 - Fijar tipo y limpiar tambien la cache del tipo antes de cargar el objeto.
     1161        FONE_set_erp_product_type($_product_id, 'variable');
    11021162
    11031163        // 3) Usa SIEMPRE el tipo correcto
    … …  
    11271187        FONE_delete_product_image_variaciones($_product_id);
    11281188
    1129         wp_remove_object_terms($_product_id, 'simple', 'product_type');
    1130         wp_remove_object_terms($_product_id, 'variable', 'product_type');
    1131 
    1132         if (get_term_by('slug','composite','product_type')) {
    1133             wp_set_object_terms($_product_id, 'composite', 'product_type', false);
    1134         }
     1189        // GPT0000000004 - El objeto ya se guardo como compuesto; mantener tipo y cache coherentes.
     1190        FONE_set_erp_product_type($_product_id, 'composite');
    11351191
    11361192        FONE_componentes($_product_id, $compuestos);
    … …  
    11491205        FONE_delete_product_image_variaciones($_product_id);
    11501206
    1151         wp_remove_object_terms($_product_id, 'variable', 'product_type');
    1152 
    1153         if (get_term_by('slug','composite','product_type')) {
    1154             wp_remove_object_terms($_product_id, 'composite', 'product_type');
    1155         }
    1156 
    1157         wp_set_object_terms($_product_id, 'simple', 'product_type', false);
     1207        // GPT0000000004 - Conversion general a simple, tambien al venir de variable o compuesto.
     1208        FONE_set_erp_product_type($_product_id, 'simple');
    11581209        delete_post_meta($_product_id, 'wooco_components');
    11591210
    … …  
    14971548              AND p.post_parent = %d
    14981549              AND p.post_status IN ('publish','draft','private','pending')
     1550
    14991551              AND pm.meta_key = 'fone_idfactura'
    15001552              AND pm.meta_value = %s
    … …  
    17031755    FONE_ensure_parent_attributes_and_terms($_product_id, $terms_by_attr);
    17041756
     1757    // GPT0000000002 - Conservar el contexto del padre para restaurarlo tras las variaciones.
     1758    $fone_import_parent_context = $GLOBALS['FONE_import_error_context'] ?? null;
    17051759    // 6) Procesa variaciones entrantes (update/create) + aplica lógica de activación
    17061760    $incoming_ids = [];
    … …  
    17941848        }
    17951849
     1850        // GPT0000000002 - La clave sku de esta lista es el identificador ERP de la variacion.
     1851        if (is_array($fone_import_parent_context) && function_exists('FONE_ajax_product_context')) {
     1852            FONE_ajax_product_context(array('erp_id'=>$idf, 'name'=>($fone_import_parent_context['name'] ?? '').' / '.implode(' / ', $attr_values),
     1853                'sku'=>'', 'woo_id'=>$existing_by_idfactura[$idf] ?? '', 'parent_id'=>$_product_id));
     1854        }
    17961855        // UPDATE
    17971856        if (isset($existing_by_idfactura[$idf])) {
    … …  
    18771936    }
    18781937
     1938    // GPT0000000002 - Los pasos posteriores vuelven a corresponder al producto padre.
     1939    if (is_array($fone_import_parent_context)) { $GLOBALS['FONE_import_error_context'] = $fone_import_parent_context; }
    18791940    // 7) Variaciones que EXISTEN en Woo pero ya NO vienen del ERP => borrar definitivamente
    18801941    // IMPORTANTE:
    … …  
    21052166    // Creating the product variation
    21062167    $variation_id = wp_insert_post( $variation_post );
     2168    // GPT0000000002 - Identificar una variacion recien creada antes de cargarla.
     2169    if (function_exists('FONE_ajax_product_context') && !is_wp_error($variation_id)) { FONE_ajax_product_context(array('woo_id'=>(int)$variation_id)); }
    21072170
    21082171    // Get an instance of the WC_Product_Variation object
    … …  
    23542417    $name=preg_replace('/\s+/u',' ',$name);
    23552418    $n=function_exists('mb_strtolower')?mb_strtolower($name,'UTF-8'):strtolower($name);
     2419
    23562420
    23572421    $terms=get_terms(['taxonomy'=>$taxonomy,'hide_empty'=>false,'parent'=>$parent_id,'number'=>0]);
Note: See TracChangeset for help on using the changeset viewer.