name: Printful M2580 Embroidery Hoodie description: Complete pipeline for Cotton Heritage M2580 (catalog 380) PREMIUM embroidered pullover hoodies on Printful. Covers embroidery product creation with 3 placements (chest_center + wrist_left + wrist_right — chest_center/chest_left MUTUALLY EXCLUSIVE), thread color selection, variant management, auto-preview mockups, and Supabase integration. Use when creating embroidered M2580 hoodie products or managing PREMIUM tier embroidered pullover hoodies. Light colors only (White, Bone) — EU Latvia fulfillment. VERIFIED pipeline with Origin product (2026-03-03).
Printful M2580 Embroidery Hoodie — Complete Pipeline
Product Overview
| Spec | Valor |
|---|---|
| Modelo | Cotton Heritage M2580 Unisex Premium Pullover Hoodie |
| Catalog ID | 380 (mismo que DTG, pero technique = EMBROIDERY) |
| Technique | EMBROIDERY (no DTG) |
| Material | 100% cotton face / 65% ring-spun cotton, 35% polyester |
| Fit | Classic streetwear, kangaroo pocket, 3-panel hood |
| Tallas | S, M, L, XL, 2XL, 3XL (6 tallas) |
| Colores | White (#ffffff), Bone (#f5e8ce) — SOLO claros |
| EU Fulfillment | Latvia (EU_LV) — in_stock ambos colores |
| Base cost | $21.25 (S-XL), $22.55 (2XL), $23.85 (3XL) |
| Embroidery cost | +$2.60 por placement (3 placements = +$7.80) |
| Total cost | $29.05 (S-XL), $30.35 (2XL), $31.65 (3XL) |
| Retail | €59.99 (S-XL), €64.99 (2XL), €69.99 (3XL) |
| Margin | ~51.6% (S-XL), ~53.3% (2XL), ~54.8% (3XL) |
Instructions
Diferencia con M2580 DTG (skill printful-m2580)
Este skill usa el MISMO blank (M2580, catalog 380) pero con technique: EMBROIDERY en vez de DTG. Las diferencias clave:
| Aspecto | M2580 DTG | M2580 Embroidery |
|---|---|---|
| Technique | DTG printing | EMBROIDERY |
| Colores garment | Dark (9 EU) | Light (White, Bone) |
| Placements | front, back, sleeve, label | embroidery_chest_left, chest_center, wrist_left, wrist_right |
| Diseño | PNG raster @150dpi | PNG raster @300dpi (digitized) |
| Max colores diseño | Ilimitado | 15 colores de hilo (o unlimited +$3.25/placement) |
| Base cost | $21.25 | $21.25 + $10.40 embroidery |
| Uso | Diseños meme/gráficos | Branding premium, logos, motivos geométricos |
Pre-requisitos
- Cuenta Printful con API token y Store ID
- Supabase con tablas
productsyproduct_variants - Diseños de bordado en formato PNG @300dpi (ver BRANDING.md)
- Diseños deben respetar límites de colores de hilo (ver BRANDING.md)
Pipeline Completo — VERIFICADO (Origin, 2026-03-03)
Script de referencia:
frontend/scripts/create-origin-printful.mjs
Paso 1: Diseñar bordados (3 placements)
CRITICAL: embroidery_chest_center y embroidery_chest_left son MUTUAMENTE EXCLUYENTES. No se pueden usar juntos. Elegir UNO.
| Placement | Canvas | DPI | Diseño tipo |
|---|---|---|---|
embroidery_chest_center |
3000×1800 | 300 | Branding principal (SKAPARA + año) |
embroidery_wrist_left |
600×900 | 300 | Logo S mark |
embroidery_wrist_right |
600×900 | 300 | Diseño geométrico/decorativo |
Alternativa (si se usa chest_left en vez de center):
| embroidery_chest_left | 1200×1200 | 300 | Icono/número |
Ver BRANDING.md para specs detalladas y design files.
Paso 2: Renderizar PNGs @300dpi
magick -density 300 -background transparent design.svg -resize WxH! design.png
Cada PNG debe coincidir exactamente con las dimensiones del canvas del placement.
Paso 3: Subir PNGs a Supabase Storage (URL pública)
Printful NO acepta data URLs ni base64. Necesita una URL pública accesible.
// Subir a Supabase Storage primero
await supabase.storage.from('designs').upload(
'embroidery-sources/product-name/placement.png',
pngBuffer,
{ contentType: 'image/png', upsert: true }
);
const publicUrl = `${SB_URL}/storage/v1/object/public/designs/embroidery-sources/product-name/placement.png`;
Paso 4: Subir a Printful File Library
const result = await pf('/files', {
method: 'POST',
body: JSON.stringify({
url: publicUrl, // URL pública de Supabase Storage
filename: 'origin-embroidery_chest_center.png',
}),
});
const fileId = result.result.id; // Guardar para paso 5
Rate limit: delay(3000) entre uploads.
Shared utility: For scripts, use import { createPrintfulClient } from './lib/printful-rate-limiter.mjs' — handles token bucket, 429 retry with jitter, proactive slowdown, and exponential backoff automatically.
Paso 5: Crear Sync Product en Printful
const product = await pf('/store/products', {
method: 'POST',
body: JSON.stringify({
sync_product: {
name: 'Origin',
thumbnail: publicUrlChestCenter, // URL pública, NO data URL
},
sync_variants: variants.map(v => ({
variant_id: v.variant_id,
retail_price: PRICES[v.size],
is_enabled: true,
files: [
{ type: 'embroidery_chest_center', id: chestCenterFileId },
{ type: 'embroidery_wrist_left', id: wristLeftFileId },
{ type: 'embroidery_wrist_right', id: wristRightFileId },
],
options: [
{ id: 'thread_colors_chest_center', value: ['#000000', '#6B5294', '#CC3333'] },
{ id: 'thread_colors_wrist_left', value: ['#000000'] },
{ id: 'thread_colors_wrist_right', value: ['#000000', '#6B5294', '#CC3333'] },
],
})),
}),
});
Ver VARIANTS.md para la tabla completa de variant_ids por color × talla.
Thread colors format: thread_colors_<placement_sin_embroidery_>. El ID de la opción NO incluye embroidery_:
thread_colors_chest_center (no embroidery_chest_center)
thread_colors_wrist_left (no embroidery_wrist_left)
thread_colors_wrist_right (no embroidery_wrist_right)
Paso 6: GPSR — Supabase product_details
NOTA: El endpoint API de GPSR (/store/products/{id}/gpsr.json) devuelve 404 para productos de bordado. GPSR se maneja en Supabase product_details.safety_information.
Copiar la safety_information del template Printful (obtenible de otros productos DTG) y guardarla en el JSON de product_details.
Paso 7: Crear producto en Supabase
await supabase.from('products').insert({
id: crypto.randomUUID(),
title: 'Origin',
description: 'Descripción creativa EN',
category: 'pullover-hoodies',
base_price_cents: 5999, // base = S-XL price
compare_at_price_cents: 6499, // Original price (strikethrough) — must be > base_price_cents
currency: 'EUR',
status: 'active',
pod_provider: 'printful', // IMPORTANT: 'printful', no 'printify'
provider_product_id: String(pfProductId),
product_template_id: '380',
category_id: 'cc59f09e-3391-4672-8bea-805fb0628a47', // pullover-hoodies
translations: {
es: { title: 'Origin', description: '...' },
de: { title: 'Origin', description: '...' },
},
product_details: {
brand: 'SKAPARA',
model: 'Cotton Heritage M2580',
material: '65% ring-spun cotton, 35% polyester (100% cotton face)',
print_technique: 'Embroidery',
manufacturing_country: 'LV',
safety_information: '...GPSR HTML...',
},
});
Paso 8: Crear variantes en Supabase
IMPORTANT: La tabla product_variants requiere campo title (NOT NULL).
variants.push({
product_id: PRODUCT_UUID,
title: `Origin / ${color} / ${size}`, // REQUIRED
color,
size,
price_cents: PRICES[size], // 5999, 6499, 6999
is_enabled: true,
is_available: true,
external_variant_id: String(VARIANT_IDS[color][size]),
});
Paso 9: Ghost Mockups (Transparente)
Generar 4 vistas × 2 colores = 8 mockups Ghost. Ver MOCKUPS.md para el pipeline completo, estructura de respuesta y extracción de vistas.
Clave: Todos los placements devuelven las mismas URLs. Front = mockup_url, resto = extra[].option (Back/Left/Right). Usar solo mockups[0].
Paso 10: Actualizar imágenes en Supabase
Alt text usa hyphen (-), no em dash — requerido por buildImageMap() en la API.
await supabase.from('products').update({
images: [
{ src: '.../white-front.png', alt: 'Title - White' },
{ src: '.../white-left.png', alt: 'Title - White - Left' },
{ src: '.../white-back.png', alt: 'Title - White - Back' },
{ src: '.../white-right.png', alt: 'Title - White - Right' },
{ src: '.../bone-front.png', alt: 'Title - Bone' },
{ src: '.../bone-left.png', alt: 'Title - Bone - Left' },
{ src: '.../bone-back.png', alt: 'Title - Bone - Back' },
{ src: '.../bone-right.png', alt: 'Title - Bone - Right' },
],
}).eq('id', productId);
Pricing Breakdown (3 placements — VERIFICADO)
| Talla | Base | 3× Embroidery | Total Cost | Retail | Margin |
|---|---|---|---|---|---|
| S-XL | $21.25 | $7.80 | $29.05 | €59.99 | 51.6% |
| 2XL | $22.55 | $7.80 | $30.35 | €64.99 | 53.3% |
| 3XL | $23.85 | $7.80 | $31.65 | €69.99 | 54.8% |
Con label_inside adicional (+$0.99):
| Talla | Total Cost | Retail | Margin |
|---|---|---|---|
| S-XL | $30.04 | €59.99 | 49.9% |
| 2XL | $31.34 | €64.99 | 51.8% |
| 3XL | $32.64 | €69.99 | 53.4% |
Known Issues — VERIFICADOS (Origin, 2026-03-03)
- chest_center / chest_left MUTUAMENTE EXCLUYENTES: Printful rechaza productos que usan ambos. Error:
"Placement embroidery_chest_center cannot be used with placement: embroidery_chest_left". Elegir UNO de los dos. - Printful NO acepta data URLs: El endpoint
/filesrechaza base64/data URLs. Error:"file URL is not a valid URL". SIEMPRE subir primero a Supabase Storage y usar la URL pública. - GPSR endpoint 404 para embroidery:
GET /store/products/{id}/gpsr.jsondevuelve 404. GPSR debe gestionarse directamente en Supabaseproduct_details.safety_information. - Ghost mockup response: Todos los placements devuelven las mismas URLs. Extraer vistas de
mockups[0]: front=mockup_url, resto=extra[].option. Ver MOCKUPS.md. - thread_colors obligatorio: Sin especificar colores de hilo, Printful usa defaults que pueden no coincidir. SIEMPRE especificar
thread_colors_<placement>en options. - thread_colors ID format: El ID de la opción NO lleva prefijo
embroidery_. Correcto:thread_colors_chest_center, incorrecto:thread_colors_embroidery_chest_center. - product_variants.title NOT NULL: Supabase requiere campo
titleen product_variants. Usar formato"ProductName / Color / Size". - Mismo catalog_id DTG/Embroidery: Comparten catalog 380. La diferencia está en los
file.typeusados (embroidery_*vsfront/back/sleeve). - full_color pricing: "Unlimited color" (+$3.25/placement) solo para
embroidery_chest_leftyembroidery_chest_center, NO para muñecas. - Printfiles endpoint vacío:
GET /mockup-generator/printfiles/380no mapea placements de bordado. Los printfile IDs se infieren del M2475 (674).
Producto de Referencia — Origin
| Campo | Valor |
|---|---|
| Printful Product ID | 422171595 |
| Supabase Product ID | a526af13-71b0-4f8f-95eb-27dcd92cb40e |
| Printful File IDs | chest_center=950723276, wrist_left=950723322, wrist_right=950723335 |
| Placements | chest_center + wrist_left + wrist_right (3) |
| Thread colors | Black #000000, Purple #6B5294, Red #CC3333 |
| Variantes | 12 (2 colores × 6 tallas) |
| Script | frontend/scripts/create-origin-printful.mjs |