Cómo introducir los costes de 500 productos sin 500 clics
El núcleo de WooCommerce te da un campo de coste por producto y ninguna edición masiva. Con quinientos productos no es una tarde de trabajo, es un motivo para no activar nunca la función. Aquí está el camino, desde una hoja de cálculo, mediante un CSV, en un solo script.
Actualizado 2026-10-23
Esta página da por hecho que la función de precio de coste ya está activada. Si no lo está, la guía para activarla es más corta que esta página y conviene leerla antes: mientras el interruptor esté desactivado, los costes que introduzcas no llegan a ningún pedido.
Primero, calcula cuántos productos necesitas de verdad
El instinto es rellenarlo todo. Es el orden más caro posible, porque el primer ochenta por ciento del valor entra en la primera hora y el resto se lleva el resto de la semana.
Un informe de beneficios no necesita un catálogo completo. Necesita cobertura: qué parte de los ingresos tiene un coste conocido detrás. Una tienda que conoce el coste de los cincuenta productos que hacen el 80 % de la facturación tiene un informe útil; una tienda que conoce el coste de cuatrocientos productos de la cola larga y no el de sus diez más vendidos no tiene nada.
Así que solo hay un orden sensato: ordena el catálogo por ingresos y ve bajando, no por orden alfabético ni por fecha de alta. Detente cuando la cobertura deje de subir lo bastante rápido como para compensar el tecleo.
Introducir costes no cambia los pedidos que ya están en la base de datos. El coste se escribe en la línea del pedido en el momento de la venta, así que lo que introduces hoy funciona de hoy en adelante. No es un motivo para esperar: es un motivo para hacerlo esta semana y no el próximo trimestre.
La hoja de cálculo: tres columnas y una decisión
Descarga la plantilla CSV o haz la tuya; lo que importa es la estructura:
id,sku,cost
1042,,41.50
,TSHIRT-XL-BLACK,24.00
1198,,
idosku: una de las dos, no las dos. El script usaidy, si está vacío, busca el producto porsku.cost: coste neto de una unidad, con punto como separador decimal. Sin moneda, sin espacios, sin símbolos.- Un
costvacío significa «no lo sabemos» y el script se salta esa fila. No es cortesía con los datos, es necesario: un cero guardado y un coste que falta son lo mismo en la base de datos de WooCommerce, así que escribir un cero «de momento» borra información en lugar de añadirla.
La decisión que hay que tomar una vez, antes de la primera fila: qué significa «coste» en tu tienda. ¿Solo el precio de compra? ¿Con el transporte de entrada? ¿Aranceles, embalaje, comisiones de la plataforma? Cualquiera de esas respuestas vale, y ninguna vale para la mitad del catálogo. Un margen calculado con dos definiciones distintas de coste no es comparable con nada, ni siquiera consigo mismo hace un mes. Escribe la decisión en la primera fila de la hoja para no tener que adivinarla dentro de seis meses.
De dónde sacar la lista de productos: WooCommerce → Productos → Exportar te da un CSV con las
columnas ID y SKU. Quédate con esas dos, añade una tercera y rellénala en la hoja de cálculo,
donde lo ves todo a la vez.
El script: una sola pasada por el archivo
El script de abajo funciona con WP-CLI. Lee el CSV, busca el producto, escribe el coste e informa de lo que ha hecho. Empieza en modo de prueba: lo decide la primera línea.
<?php
/**
* Fills the native Cost of Goods Sold field from a CSV file (id,sku,cost).
* Run with: wp eval-file fill-costs.php costs.csv
*/
$DRY_RUN = true; // <- switch to false only after a successful trial
$file = $args[0] ?? 'costs.csv';
$handle = fopen( $file, 'r' );
if ( ! $handle ) {
WP_CLI::error( "Cannot open file: {$file}" );
}
fgetcsv( $handle ); // header row: id,sku,cost
$written = 0;
$skipped = 0;
$failed = 0;
while ( ( $row = fgetcsv( $handle ) ) !== false ) {
list( $id, $sku, $cost ) = array_pad( $row, 3, '' );
$cost = trim( str_replace( ',', '.', $cost ) );
if ( '' === $cost ) { // empty is not zero - skip it
$skipped++;
continue;
}
if ( ! is_numeric( $cost ) || (float) $cost < 0 ) {
WP_CLI::warning( "Bad cost value: '{$cost}' (id={$id}, sku={$sku})" );
$failed++;
continue;
}
$id = (int) $id;
if ( ! $id && $sku ) {
$id = wc_get_product_id_by_sku( trim( $sku ) );
}
$product = $id ? wc_get_product( $id ) : null;
if ( ! $product ) {
WP_CLI::warning( "Product not found (id={$id}, sku={$sku})" );
$failed++;
continue;
}
if ( $DRY_RUN ) {
WP_CLI::log( sprintf( '[dry run] %d %s: %s -> %s',
$product->get_id(), $product->get_name(),
var_export( $product->get_cogs_value(), true ), $cost ) );
} else {
$product->set_cogs_value( (float) $cost );
$product->save();
WP_CLI::log( sprintf( '%d %s = %s', $product->get_id(), $product->get_name(), $cost ) );
}
$written++;
}
fclose( $handle );
WP_CLI::success( sprintf( '%s: %d, skipped (empty cost): %d, failed: %d',
$DRY_RUN ? 'To write' : 'Written', $written, $skipped, $failed ) );
Guárdalo como fill-costs.php junto a costs.csv y ejecuta:
wp eval-file fill-costs.php costs.csv
El script está aquí para leerlo, no para descargarlo con un clic, y es a propósito. Escribe en tu base de datos, así que debería pasar por tus ojos antes de pasar por tus productos.
Por qué no un bucle en la consola. El tentador wp eval llamado una vez por fila arranca WordPress
entero para cada producto: con quinientos productos es un cuarto de hora en lugar de unos segundos,
y quinientas ocasiones de que falle a medias. Un script, un arranque.
Si no tienes SSH. La misma pareja de métodos, set_cogs_value() y save(), funciona en cualquier
sitio donde puedas ejecutar PHP dentro de WordPress. La API REST lleva el mismo valor en los recursos
products y variations, como cost_of_goods_sold con values, total_value y, en las variaciones,
defined_value_is_additive. Dos cosas que conviene saber antes de escribir un bucle que la llame:
effective_value y total_value son de solo lectura, y una matriz values vacía significa un coste
cero, con todo lo que eso implica más abajo en esta página. Lee primero un producto
(GET /wp-json/wc/v3/products/<id>) y comprueba la estructura con tu propia versión.
La prueba que no conviene saltarse
- Una copia de seguridad de la base de datos. No una «exportación de productos»: una copia de seguridad. El script escribe, y guardar un producto puede tocar más de un campo.
- Primero diez filas. Copia diez líneas en
trial.csvy haz todo el proceso con ellas: en prueba, después de verdad, y luego mira esos diez productos en el escritorio. - Solo entonces
$DRY_RUN = falsecon el archivo completo. - Comprueba después, en la tienda, no en la hoja de cálculo. Haz un pedido de prueba de un producto con coste y comprueba que el pedido lo guardó. Es la única prueba que cubre toda la cadena y no solo el campo en la ficha del producto.
Variaciones: el script no lo decide por ti
Una variación hereda el coste del producto padre hasta que tiene uno propio, y cuando lo tiene, un interruptor decide si ese coste sustituye al del padre o se suma a él. Es una decisión sobre lo que significa la cifra, no sobre cuál es, así que el script no la toma.
En la práctica: rellena de forma masiva los costes de los productos padre y toca las variaciones solo donde el coste sea realmente distinto; después elige el modo en el editor de productos, a propósito, donde lo ves. Una camiseta que cuesta 24 con una variación «bordada» en modo de suma a 3 sale a 27; la misma variación en modo de sustitución sale a 3. Los detalles, en costes en variaciones.
Una cosa que conviene saber al comprobar los resultados: la columna auxiliar de WooCommerce guarda el valor introducido, no el efectivo, así que una variación que simplemente hereda el coste de su padre parece una variación sin coste en una consulta SQL. Cualquier lista de «productos sin coste» hecha directamente con esa columna estará llena de falsas alarmas.
Lo que esto no arregla
- El historial. Los pedidos hechos antes de introducir los costes se quedan sin coste para siempre; no hay relleno hacia atrás, y no es un descuido.
- Los regalos. Un producto que de verdad cuesta cero se guarda como si no tuviera coste, porque el núcleo borra el valor en lugar de escribir un cero. Se quedará en la lista de costes que faltan y no puedes hacer nada al respecto.
- Los reembolsos. Al hacer un reembolso, WooCommerce recalcula el coste de cada línea del pedido original con el coste actual del producto, así que los costes recién introducidos pueden reescribir el mismo historial que describen. Qué pasa entonces.
- Los costes variables. El envío, las comisiones de la pasarela de pago y las devoluciones al proveedor no son coste de los productos y el campo de coste no los cubre. El beneficio bruto no es el beneficio neto, y no fingimos lo contrario.
Lista de comprobación
- La función de coste activada: sin ella, nada de lo demás sirve.
- Una definición de coste, escrita en la hoja.
- Exportar productos →
id,skuy una columnacostvacía. - Ordenar por ingresos y rellenar desde arriba.
- Copia de seguridad, prueba con diez filas y solo después el archivo completo.
- Un pedido de prueba para comprobar toda la cadena.
- Las variaciones aparte y a propósito: modo de sustitución o modo de suma.
Si algún paso se comporta en tu tienda de forma distinta a lo descrito aquí, merece la pena que nos escribas: estas páginas se corrigen cuando resultan estar equivocadas.