from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging

_logger = logging.getLogger(__name__)


class SaleOrder(models.Model):
    _inherit = 'sale.order'

    shopify_order_id = fields.Char(string='ID Pedido Shopify', copy=False, index=True)
    shopify_order_number = fields.Char(string='Número Pedido Shopify', copy=False)

    # ── Fulfillment ────────────────────────────────────────────────────────────
    shopify_fulfillment_id = fields.Char(
        string='ID Fulfillment Shopify',
        copy=False,
        readonly=True,
        help="ID del fulfillment creado en Shopify al marcar el pedido como enviado.",
    )
    shopify_fulfillment_status = fields.Selection(
        selection=[
            ('pending',   'Pendiente'),
            ('fulfilled', 'Enviado ✓'),
            ('partial',   'Parcial'),
            ('restocked', 'Reabastecido'),
            ('error',     'Error'),
        ],
        string='Estado Fulfillment Shopify',
        copy=False,
        readonly=True,
        default=False,
        help="Estado de fulfillment sincronizado con Shopify.",
    )

    # ── Reembolsos y Devoluciones ──────────────────────────────────────────────
    shopify_financial_status = fields.Char(
        string='Estado Financiero Shopify',
        readonly=True,
        copy=False,
        help="Estado de pago real importado de Shopify (paid, refunded, partially_refunded, etc.)"
    )
    shopify_refund_status = fields.Selection(
        selection=[
            ('no_refund', 'Sin Reembolso'),
            ('partially_refunded', 'Parcial ⚠'),
            ('refunded', 'Total ✓'),
        ],
        string='Reembolso Shopify',
        default='no_refund',
        readonly=True,
        copy=False,
    )
    shopify_refund_ids = fields.Text(
        string='IDs Reembolsos Shopify',
        readonly=True,
        copy=False,
        help="Lista de IDs de reembolsos detectados en Shopify."
    )
    shopify_refund_ids_processed = fields.Text(
        string='IDs Reembolsos Automatizados',
        readonly=True,
        copy=False,
        help="Lista de IDs de reembolsos que ya ejecutaron la creación automática de Nota de Crédito y Devolución de Stock."
    )
    shopify_refund_amount = fields.Float(
        string='Monto Reembolsado Shopify',
        readonly=True,
        copy=False
    )
    shopify_shipping_title = fields.Char(
        string='Método de Envío Shopify',
        readonly=True,
        copy=False,
        help="Método de entrega seleccionado por el cliente en Shopify (ej: Envío DHL, OFICINA HRSGOLF, etc.)"
    )

    def action_view_shopify_refunds(self):
        """Método para el botón de estado de reembolso en la orden."""
        self.ensure_one()
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'Reembolsos de Shopify',
                'message': f"Monto reembolsado registrado en Shopify: ${self.shopify_refund_amount:.2f} MXN.\n"
                           f"Estado financiero actual: {self.shopify_financial_status or 'Desconocido'}.",
                'type': 'info',
                'sticky': False,
            }
        }

    def action_confirm(self):
        res = super(SaleOrder, self).action_confirm()
        for order in self:
            if order.shopify_order_id:
                title = (order.shopify_shipping_title or '').upper()
                if any(x in title for x in ['OFICINA', 'RETIRO', 'PICKUP', 'SUCURSAL', 'TIENDA']):
                    try:
                        order._create_pickup_internal_transfer()
                    except Exception as e:
                        _logger.error("[Shopify BOPIS] Error al crear traspaso interno automático para '%s': %s", order.name, str(e))
        return res

    def action_cancel(self):
        res = super(SaleOrder, self).action_cancel()
        for order in self:
            # Buscar cualquier albarán/traslado asociado por sale_id o por origin (documento origen)
            pending_pickings = self.env['stock.picking'].search([
                '|', ('sale_id', '=', order.id), ('origin', '=', order.name),
                ('state', 'not in', ['done', 'cancel'])
            ])
            if pending_pickings:
                _logger.info("[Shopify Sync] Cancelando traslados/albaranes pendientes para el pedido '%s' debido a cancelación", order.name)
                try:
                    pending_pickings.action_cancel()
                except Exception as e:
                    _logger.warning("[Shopify Sync] No se pudo cancelar el albarán asociado al pedido '%s': %s", order.name, str(e))
        return res

    def _create_pickup_internal_transfer(self):
        self.ensure_one()
        _logger.info("[Shopify BOPIS] Iniciando creación de traspaso interno automático para pedido '%s'", self.name)
        
        # 1. Resolver ubicaciones
        # Almacén destino: el asignado en el pedido (OFICINA HRSGOLF)
        warehouse_dest = self.warehouse_id
        if not warehouse_dest or 'OFICINA' not in warehouse_dest.name.upper():
            warehouse_dest = self.env['stock.warehouse'].search([('name', 'ilike', 'OFICINA')], limit=1)
            
        if not warehouse_dest:
            _logger.warning("[Shopify BOPIS] No se encontró el almacén de la Oficina. Omitiendo traspaso.")
            return False
            
        # Almacén origen: Bodega central (ALMA)
        warehouse_orig = self.env['stock.warehouse'].search([('code', '=', 'ALMA')], limit=1)
        if not warehouse_orig:
            warehouse_orig = self.env['stock.warehouse'].search([('id', '!=', warehouse_dest.id)], limit=1)
            
        if not warehouse_orig or warehouse_orig.id == warehouse_dest.id:
            _logger.warning("[Shopify BOPIS] No se encontró almacén de origen distinto de la Oficina. Omitiendo traspaso.")
            return False
            
        location_orig = warehouse_orig.lot_stock_id
        location_dest = warehouse_dest.lot_stock_id
        
        # 2. Buscar Tipo de Operación de Transferencia Interna de la Bodega Central
        picking_type = self.env['stock.picking.type'].search([
            ('warehouse_id', '=', warehouse_orig.id),
            ('code', '=', 'internal'),
            ('sequence_code', '=', 'INT')
        ], limit=1)
        if not picking_type:
            # Fallback a cualquier tipo de operación interno si no hay uno con secuencia 'INT'
            picking_type = self.env['stock.picking.type'].search([
                ('warehouse_id', '=', warehouse_orig.id),
                ('code', '=', 'internal')
            ], limit=1)
            
        if not picking_type:
            _logger.warning("[Shopify BOPIS] No se encontró tipo de operación 'Transferencia Interna' para el almacén %s", warehouse_orig.name)
            return False

        # 3. Buscar movimientos internos ya existentes para este pedido (para evitar duplicados a nivel de producto)
        existing_internal_moves = self.env['stock.move'].search([
            '|', ('picking_id.sale_id', '=', self.id), ('picking_id.origin', '=', self.name),
            ('picking_id.picking_type_id.code', '=', 'internal'),
            ('state', '!=', 'cancel')
        ])
        existing_product_ids = existing_internal_moves.mapped('product_id.id')
            
        # 4. Filtrar las líneas del pedido que necesitan un nuevo traspaso interno (productos no transferidos aún)
        lines_to_transfer = self.env['sale.order.line']
        for line in self.order_line:
            if line.display_type or not line.product_id or line.product_id.type != 'product':
                continue
            if line.product_id.id not in existing_product_ids:
                lines_to_transfer |= line

        if not lines_to_transfer:
            _logger.info("[Shopify BOPIS] Todos los productos del pedido '%s' ya cuentan con un traspaso interno. Omitiendo creación.", self.name)
            return False

        # 5. Crear el albarán de traspaso interno
        picking_vals = {
            'picking_type_id': picking_type.id,
            'location_id': location_orig.id,
            'location_dest_id': location_dest.id,
            'origin': self.name,
            'sale_id': self.id,
            'move_type': 'direct',
        }
        picking = self.env['stock.picking'].create(picking_vals)
        
        # 6. Crear las líneas de movimiento (stock.move)
        for line in lines_to_transfer:
            move_vals = {
                'name': line.product_id.display_name,
                'product_id': line.product_id.id,
                'product_uom_qty': line.product_uom_qty,
                'product_uom': line.product_uom.id,
                'picking_id': picking.id,
                'location_id': location_orig.id,
                'location_dest_id': location_dest.id,
                'warehouse_id': warehouse_orig.id,
            }
            self.env['stock.move'].create(move_vals)
            
        # 7. Confirmar y reservar stock
        if picking.move_ids:
            picking.action_confirm()
            picking.action_assign()
            _logger.info("[Shopify BOPIS] Traspaso interno '%s' creado y reservado para productos del pedido '%s'", picking.name, self.name)

            # ── ALERTA 4: Detectar stock insuficiente para traslado ──
            unreserved_moves = picking.move_ids.filtered(
                lambda m: m.state not in ('assigned', 'done', 'cancel') and m.product_uom_qty > 0
            )
            if unreserved_moves:
                alert_lines = []
                for move in unreserved_moves:
                    available = int(move.product_id.with_context(
                        location=move.location_id.id
                    ).qty_available)
                    alert_lines.append(
                        f"&nbsp;&nbsp;📉 {move.product_id.name} "
                        f"(SKU: {move.product_id.default_code or 'N/A'}): "
                        f"Necesita {int(move.product_uom_qty)}, Disponible: {available}"
                    )
                self.message_post(
                    body=(
                        "⚠️ <b>Stock Insuficiente para Traslado</b><br/><br/>"
                        f"El traspaso interno <b>{picking.name}</b> fue creado pero "
                        f"<b>NO pudo reservar</b> stock completo:<br/>"
                        + "<br/>".join(alert_lines)
                        + "<br/><br/>⚡ <b>Acción requerida:</b> Coordina con almacén para "
                        "verificar disponibilidad o sustituir el producto desde Shopify."
                    ),
                    subtype_xmlid="mail.mt_note",
                    message_type='notification',
                )
                _logger.warning(
                    "[Shopify BOPIS] ⚠️ Traspaso '%s' para pedido '%s' tiene %s productos sin stock suficiente.",
                    picking.name, self.name, len(unreserved_moves)
                )

            return picking
        else:
            picking.unlink()
            _logger.warning("[Shopify BOPIS] No se generaron líneas de stock. Se eliminó el albarán de traspaso.")
            return False

    def _update_pickup_internal_transfer_qty(self, product, old_qty, new_qty):
        """
        Actualiza la cantidad en el traslado interno (BOPIS) cuando se edita un pedido en Shopify.
        Calcula matemáticamente la cantidad total ya transferida o en proceso, y realiza ajustes
        exactos para prevenir duplicaciones en caso de múltiples ediciones consecutivas.
        """
        self.ensure_one()

        # Buscar todos los movimientos internos (BOPIS) activos para este producto
        existing_moves = self.env['stock.move'].search([
            '|', ('picking_id.sale_id', '=', self.id), ('picking_id.origin', '=', self.name),
            ('picking_id.picking_type_id.code', '=', 'internal'),
            ('product_id', '=', product.id),
            ('state', '!=', 'cancel'),
        ])

        if not existing_moves:
            _logger.info("[Shopify BOPIS Qty] No hay traslados internos para %s. Omitiendo.", product.display_name)
            return

        # Calcular la cantidad total que ya está en traslados (hechos o pendientes)
        total_transfer_qty = sum(existing_moves.mapped('product_uom_qty'))
        true_diff = new_qty - total_transfer_qty

        if true_diff == 0:
            _logger.info("[Shopify BOPIS Qty] Cantidad balanceada para %s (Total: %s).", product.display_name, new_qty)
            return

        if true_diff > 0:
            # ── Faltan unidades: Buscar un traslado pendiente o crear uno nuevo ──
            pending_moves = existing_moves.filtered(lambda m: m.picking_id.state in ('draft', 'waiting', 'confirmed', 'assigned'))

            if pending_moves:
                # Sumar la diferencia al traslado pendiente existente
                move = pending_moves[0]
                picking = move.picking_id
                new_move_qty = move.product_uom_qty + true_diff

                _logger.info("[Shopify BOPIS Qty] Actualizando traslado pendiente '%s' (+%s unidades)", picking.name, true_diff)
                move.write({'product_uom_qty': new_move_qty})
                if picking.state == 'assigned':
                    picking.action_assign()

                picking.message_post(
                    body=(
                        f"🔄 <b>Actualización por Edición en Shopify</b><br/><br/>"
                        f"La cantidad requerida de <b>{product.display_name}</b> aumentó.<br/>"
                        f"Se añadieron <b>+{int(true_diff)} unidad(es)</b> a este traslado "
                        f"(Pasó de {int(move.product_uom_qty - true_diff)} a {int(new_move_qty)})."
                    ),
                    subtype_xmlid="mail.mt_note",
                    message_type='notification',
                    author_id=self.env.ref('base.partner_root').id,
                )
            else:
                # Todos están HECHOS. Crear traslado complementario por la verdadera diferencia.
                base_picking = existing_moves[0].picking_id
                _logger.info("[Shopify BOPIS Qty] Creando traslado complementario de +%s para %s", true_diff, product.display_name)
                
                comp_picking_vals = {
                    'picking_type_id': base_picking.picking_type_id.id,
                    'location_id': base_picking.location_id.id,
                    'location_dest_id': base_picking.location_dest_id.id,
                    'origin': self.name,
                    'sale_id': self.id,
                    'move_type': 'direct',
                }
                comp_picking = self.env['stock.picking'].create(comp_picking_vals)
                self.env['stock.move'].create({
                    'name': f"[Complemento Shopify Edit] {product.display_name}",
                    'product_id': product.id,
                    'product_uom_qty': true_diff,
                    'product_uom': existing_moves[0].product_uom.id,
                    'picking_id': comp_picking.id,
                    'location_id': base_picking.location_id.id,
                    'location_dest_id': base_picking.location_dest_id.id,
                    'warehouse_id': existing_moves[0].warehouse_id.id if existing_moves[0].warehouse_id else False,
                })
                comp_picking.action_confirm()
                comp_picking.action_assign()

                self.message_post(
                    body=(
                        f"📦 <b>Traslado Complementario Creado</b><br/><br/>"
                        f"Shopify editó la cantidad de <b>{product.display_name}</b> a {int(new_qty)}.<br/>"
                        f"Se creó el traslado complementario <b>{comp_picking.name}</b> por "
                        f"<b>+{int(true_diff)} unidad(es)</b>."
                    ),
                    subtype_xmlid="mail.mt_note",
                    message_type='notification',
                )
                comp_picking.message_post(
                    body=(
                        f"📦 <b>Traslado Complementario Automático</b><br/><br/>"
                        f"El pedido <b>{self.name}</b> fue editado en Shopify.<br/>"
                        f"Se requieren <b>+{int(true_diff)} unidad(es)</b> adicionales de "
                        f"<b>{product.display_name}</b> respecto a los traslados anteriores."
                    ),
                    subtype_xmlid="mail.mt_note",
                    message_type='notification',
                    author_id=self.env.ref('base.partner_root').id,
                )

        elif true_diff < 0:
            # ── Sobran unidades (El cliente redujo la cantidad) ──
            # Se requiere atención manual independientemente de si el traslado está pendiente o no
            base_picking = existing_moves[0].picking_id
            _logger.warning("[Shopify BOPIS Qty] La cantidad disminuyó. Sobran %s unidades de %s.", abs(true_diff), product.display_name)
            
            self.message_post(
                body=(
                    f"⚠️ <b>Atención: Reducción de Cantidad en Retiro de Oficina</b><br/><br/>"
                    f"Shopify redujo la cantidad de <b>{product.display_name}</b> a {int(new_qty)}.<br/>"
                    f"Actualmente hay {int(total_transfer_qty)} unidades en traslados internos (pendientes o completados).<br/>"
                    f"⚡ <b>Acción requerida:</b> Debes cancelar u organizar la devolución de "
                    f"<b>{int(abs(true_diff))} unidad(es)</b> de forma manual."
                ),
                subtype_xmlid="mail.mt_note",
                message_type='notification',
                author_id=self.env.ref('base.partner_root').id,
            )

    # ── Guía de Envío ──────────────────────────────────────────────────────────
    shopify_tracking_number = fields.Char(
        string='Número de Guía',
        copy=False,
        help="Número de guía del paquetero (DHL, FedEx, etc.). "
             "Se enviará a Shopify junto con el fulfillment para que el cliente pueda rastrear su pedido.",
    )
    shopify_carrier_name = fields.Selection(
        selection=[
            ('DHL',    'DHL Express'),
            ('FedEx',  'FedEx'),
            ('Estafeta', 'Estafeta'),
            ('Redpack', 'Redpack'),
            ('Paquetexpress', 'Paquetexpress'),
            ('J&T',    'J&T Express'),
            ('MensajeriaLocal', 'Mensajería Local (Taxi/Mensajero)'),
            ('Other',  'Otro'),
        ],
        string='Paquetería',
        copy=False,
        help="Paquetería con la que se realiza el envío. "
             "Usa 'Mensajería Local' para envíos por taxi o mensajero de confianza.",
    )

    def action_notify_shopify_fulfillment(self):
        """
        Botón manual: marca el pedido como 'Fulfilled' en Shopify
        e incluye el número de guía si está capturado.

        Flujo:
        1. Verifica que el pedido viene de Shopify (tiene shopify_order_id).
        2. Verifica que al menos una entrega está validada (state='done').
        3. Toma el número de guía del campo shopify_tracking_number (o del albarán si no).
        4. Llama _notify_shopify_fulfillment() en el config.
        """
        self.ensure_one()

        if not self.shopify_order_id:
            raise UserError(
                "Este pedido no está vinculado a Shopify.\n"
                "Solo se pueden enviar fulfillments de pedidos importados desde Shopify."
            )

        if self.shopify_fulfillment_status == 'fulfilled':
            raise UserError(
                "Este pedido ya fue marcado como 'Enviado' en Shopify (fulfillment_id: %s).\n"
                "No es necesario enviarlo de nuevo." % self.shopify_fulfillment_id
            )

        # Verificar que haya al menos una entrega completada
        done_pickings = self.picking_ids.filtered(lambda p: p.state == 'done')
        if not done_pickings:
            raise UserError(
                "No hay entregas validadas para este pedido.\n"
                "Valida la entrega en Odoo antes de notificar el fulfillment a Shopify."
            )

        # Buscar el shopify.config activo
        config = self.env['shopify.config'].search([], limit=1)
        if not config:
            raise UserError("No hay configuración de Shopify disponible.")

        # Prioridad: campo directo en la orden → campo en el albarán (módulo delivery)
        tracking_number = self.shopify_tracking_number or None
        carrier_name = self.shopify_carrier_name or None

        if not tracking_number:
            # Fallback: leer del albarán si el módulo delivery está instalado
            for picking in done_pickings:
                if hasattr(picking, 'carrier_tracking_ref') and picking.carrier_tracking_ref:
                    tracking_number = picking.carrier_tracking_ref
                if hasattr(picking, 'carrier_id') and picking.carrier_id:
                    carrier_name = picking.carrier_id.name

        return config._notify_shopify_fulfillment(
            sale_order=self,
            tracking_number=tracking_number,
            carrier_name=carrier_name,
            notify_customer=True,
        )


class StockPicking(models.Model):
    _inherit = 'stock.picking'

    shopify_order_number = fields.Char(
        string='Número Shopify',
        compute='_compute_shopify_fields',
        store=True,
        readonly=True,
    )
    shopify_shipping_title = fields.Char(
        string='Método Envío Shopify',
        compute='_compute_shopify_fields',
        store=True,
        readonly=True,
    )
    shopify_delivery_type = fields.Selection(
        selection=[
            ('none', 'No Shopify ⚪'),
            ('pickup', 'Retiro en Oficina 🔵'),
            ('delivery', 'Envío con Guía 🚚'),
        ],
        string='Tipo Entrega Shopify',
        compute='_compute_shopify_fields',
        store=True,
    )
    shopify_ready_for_pickup_notified = fields.Boolean(
        string='Notificado Listo para Retiro',
        default=False,
        copy=False,
    )
    shopify_delivery_address = fields.Char(
        related='partner_id.contact_address',
        string='Dirección de Envío Completa',
        readonly=True,
    )
    shopify_delivery_phone = fields.Char(
        related='partner_id.phone',
        string='Teléfono de Envío',
        readonly=True,
    )
    shopify_delivery_mobile = fields.Char(
        related='partner_id.mobile',
        string='Celular de Envío',
        readonly=True,
    )

    @api.depends('sale_id.shopify_order_id', 'sale_id.shopify_order_number', 'sale_id.shopify_shipping_title', 'origin')
    def _compute_shopify_fields(self):
        for picking in self:
            sale = picking.sale_id.sudo() if picking.sale_id else False
            if not sale and picking.origin:
                sale = self.env['sale.order'].sudo().search([('name', '=', picking.origin)], limit=1)
            
            if sale and sale.shopify_order_id:
                picking.shopify_order_number = sale.shopify_order_number
                picking.shopify_shipping_title = sale.shopify_shipping_title
                
                title = (sale.shopify_shipping_title or '').upper()
                if not title:
                    # Pedidos sin método de envío (como Punto de Venta POS)
                    picking.shopify_delivery_type = 'none'
                elif any(x in title for x in ['OFICINA', 'RETIRO', 'PICKUP', 'SUCURSAL', 'TIENDA']):
                    picking.shopify_delivery_type = 'pickup'
                else:
                    picking.shopify_delivery_type = 'delivery'
            else:
                picking.shopify_order_number = False
                picking.shopify_shipping_title = False
                picking.shopify_delivery_type = 'none'

    def action_shopify_ready_for_pickup(self):
        self.ensure_one()
        sale = self.sale_id.sudo() if self.sale_id else False
        if not sale and self.origin:
            sale = self.env['sale.order'].sudo().search([('name', '=', self.origin)], limit=1)
        
        if not sale or not sale.shopify_order_id:
            raise UserError("Este albarán no está vinculado a un pedido de Shopify.")

        config = self.env['shopify.config'].sudo().search([('state', '=', 'confirmed')], limit=1)
        if not config:
            raise UserError("No hay configuración de Shopify confirmada.")

        # Notificar a Shopify
        res = config._notify_shopify_ready_for_pickup(sale)
        if res:
            self.shopify_ready_for_pickup_notified = True
            self.message_post(
                body="✅ <strong>Shopify:</strong> Se ha notificado al cliente que el pedido está 'Listo para Retiro'.",
                subtype_xmlid="mail.mt_note"
            )
        else:
            raise UserError("Hubo un error al notificar a Shopify. Revisa los logs o verifica que la ubicación de Shopify tenga 'Local Pickup' activado.")
        return True

    def button_validate(self):
        """
        Extensión del botón Validar:
        1. Valida de forma obligatoria que se tenga número de guía si el pedido
           es de tipo envío por paquetería ('delivery') y es un albarán de salida.
        2. Si pasa la validación y el albarán se valida con éxito:
           - Si es un traspaso interno de retiro local, notifica "Listo para retiro" a Shopify.
           - Si es un albarán de salida final (entrega), notifica fulfillment automático a Shopify.
           - Inicia la sincronización de stock de los productos.
        """
        for picking in self:
            sale = picking.sale_id.sudo() if picking.sale_id else False
            if not sale and picking.origin:
                sale = self.env['sale.order'].sudo().search([('name', '=', picking.origin)], limit=1)
            if sale and sale.shopify_order_id:
                # Comprobar obligatoriedad de la guía solo para albaranes de salida (entregas)
                if picking.picking_type_id.code == 'outgoing':
                    picking._compute_shopify_fields()  # Asegurar cálculo fresco
                    if picking.shopify_delivery_type == 'delivery':
                        tracking = picking.carrier_tracking_ref or None
                        if not tracking:
                            raise UserError(
                                "⚠️  [Alerta Almacén] Este pedido de Shopify requiere ENVÍO A DOMICILIO (%s).\n\n"
                                "Por favor, ingresa el 'Referencia de guía' (Número de guía) y la paquetería "
                                "en la pestaña 'Información adicional' antes de Validar este albarán." % picking.shopify_shipping_title
                            )

        res = super(StockPicking, self).button_validate()
        
        for picking in self:
            if picking.state == 'done':
                sale = picking.sale_id.sudo() if picking.sale_id else False
                if not sale and picking.origin:
                    sale = self.env['sale.order'].sudo().search([('name', '=', picking.origin)], limit=1)
                if sale and sale.shopify_order_id:
                    config = self.env['shopify.config'].sudo().search([('state', '=', 'confirmed')], limit=1)
                    if config:
                        # A. ── TRASPASO INTERNO (Transferido en Shopify) ──
                        if picking.picking_type_id.code == 'internal':
                            title = (sale.shopify_shipping_title or '').upper()
                            if any(x in title for x in ['OFICINA', 'RETIRO', 'PICKUP', 'SUCURSAL', 'TIENDA']):
                                _logger.info(
                                    "[Shopify BOPIS] Traspaso Interno '%s' validado. "
                                    "Enviando movimiento 'Transferido al lugar de retiro' a Shopify para pedido '%s'",
                                    picking.name, sale.name
                                )
                                try:
                                    config._notify_shopify_fulfillment_order_move(sale)
                                except Exception as e:
                                    _logger.warning(
                                        "[Shopify BOPIS] Error al notificar movimiento de sucursal para '%s': %s",
                                        sale.name, str(e)
                                    )

                        # B. ── ENTREGA AL CLIENTE (Fulfillment Final / Retirado) ──
                        elif picking.picking_type_id.code == 'outgoing':
                            if sale.shopify_fulfillment_status != 'fulfilled':
                                tracking = picking.carrier_tracking_ref or None
                                carrier = picking.carrier_id.name if picking.carrier_id else None
                                
                                _logger.info(
                                    "[Shopify Auto-Fulfillment] Albarán de salida '%s' validado. "
                                    "Notificando envío/entrega automática a Shopify para pedido '%s' (Guía: %s, Paquetería: %s)",
                                    picking.name, sale.name, tracking, carrier
                                )
                                
                                try:
                                    config._notify_shopify_fulfillment(
                                        sale_order=sale,
                                        tracking_number=tracking,
                                        carrier_name=carrier,
                                        notify_customer=True,
                                    )
                                except Exception as e:
                                    _logger.warning(
                                        "[Shopify Auto-Fulfillment] Error al notificar envío automático para '%s': %s",
                                        sale.name, str(e)
                                    )
                                    # ── ALERTA 2: Fulfillment fallido → notificar en chatter ──
                                    try:
                                        sale.message_post(
                                            body=(
                                                "❌ <b>Fulfillment Shopify Fallido</b><br/><br/>"
                                                f"No se pudo marcar el pedido como enviado en Shopify "
                                                f"al validar el albarán <b>{picking.name}</b>.<br/>"
                                                f"<b>Error:</b> {str(e)}<br/><br/>"
                                                "⚡ Usa el botón <b>'Notificar Fulfillment a Shopify'</b> "
                                                "en la pestaña 'Otra Información' para reintentar."
                                            ),
                                            subtype_xmlid="mail.mt_note",
                                            message_type='notification',
                                        )
                                    except Exception:
                                        pass  # No romper el flujo si el chatter falla
                
                # 2. ── AUTO-STOCK SYNC ──
                # ELIMINADO: Ahora se maneja de forma global mediante shopify.stock.queue
                # al sobrescribir stock.quant._update_available_quantity, lo cual
                # cubre ajustes de inventario, ventas y traslados.

        return res
