> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.restauranteropro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fix: DESGLOSE DE VENTA en Corte Z

> El DESGLOSE DE VENTA del Corte Z debe calcularse desde las órdenes reales del sistema, no desde los pagos capturados por caja.

## El problema

El DESGLOSE DE VENTA del Corte Z (`#ORDER_SUBTOTAL#`, `#ORDER_TAXES#`, `#ORDER_TOTAL#`) actualmente se calcula **tomando lo que caja capturó como pago** y dividiéndolo entre 1.16 para "obtener" el subtotal.

```
FLUJO ACTUAL (incorrecto)
─────────────────────────────────────────────────────
Cajero cobra la cuenta → registra el pago en caja
Corte Z toma esos pagos (efectivo + tarjeta)
Le divide entre 1.16 para "calcular" el subtotal
Ese número aparece como DESGLOSE DE VENTA
```

### Por qué está mal

<Warning>Si el cajero teclea un monto incorrecto al cobrar, el DESGLOSE DE VENTA queda mal — y ese dato incorrecto es el que llega a Restaurantero Pro, afectando el P\&L, Food Cost y EBITDA del dashboard del dueño.</Warning>

Además de errores del cajero, hay otros problemas estructurales:

| Problema                               | Consecuencia                                                        |
| -------------------------------------- | ------------------------------------------------------------------- |
| Cancelaciones y voids no se descuentan | Ventas infladas — productos cancelados aparecen como vendidos       |
| Descuentos pueden no reflejarse        | El total reportado no coincide con lo que realmente pagó el cliente |
| IVA calculado como 16% fijo en todo    | Productos con IVA 0% (alimentos básicos) quedan mal calculados      |
| Datos incorrectos a Restaurantero Pro  | P\&L, Food Cost % y EBITDA del dashboard quedan mal                 |

***

## La fuente de verdad correcta

El DESGLOSE DE VENTA debe calcularse desde **`OrderTicketLine` con status `open`** — las líneas que realmente se ordenaron, sirvieron y cobraron correctamente.

```
FLUJO CORRECTO
─────────────────────────────────────────────────────
OrderTicketLine (status: 'open')
= lo que se ordenó y sirvió
− cancelaciones (status: 'canceled') → ya excluidas
− voids/anulaciones (status: 'voided') → ya excluidas  
− descuentos aplicados → descontados en netSubtotal
= VENTA REAL NETA
```

<Note>El método `getOrderTicketLines($model, 'open')` ya existe en el código y filtra exactamente lo correcto — solo líneas con status `open`, excluyendo automáticamente `canceled` y `voided`. Solo hay que usarlo para calcular el subtotal de ventas.</Note>

El modelo `OrderTicketLine` ya tiene los accessors correctos listos para usar:

| Accessor             | Qué representa                                                |
| -------------------- | ------------------------------------------------------------- |
| `$line->netSubtotal` | Subtotal de la línea menos descuento aplicado                 |
| `$line->tax`         | IVA calculado correctamente por línea (respeta IVA 0%)        |
| `$line->total`       | Monto final neto de la línea (con descuentos, sin cancelados) |

***

## El fix

**Archivo a modificar:**

```
app/Services/Implementations/BaseCashRegisterClosingXZOutPrinterService.php
```

**Solo hay que reemplazar 3 métodos.** Todo lo demás queda igual.

### Método 1 — `getOrderSubtotal` (línea \~277)

```php theme={null}
// ANTES (incorrecto — toma los pagos y divide entre 1.16)
protected function getOrderSubtotal(Model|CashRegisterOpening $model): float
{
    $totalPaymentMethodNet = $this->getTotalPaymentMethodNet($model);
    return $totalPaymentMethodNet / 1.16;
}

// DESPUÉS (correcto — suma netSubtotal de líneas reales)
protected function getOrderSubtotal(Model|CashRegisterOpening $model): float
{
    return $this->getOrderTicketLines($model, 'open')
        ->map(fn (OrderTicketLine $line) => $line->netSubtotal / 1.16)
        ->sum();
}
```

### Método 2 — `getOrderTaxes` (línea \~284)

```php theme={null}
// ANTES (incorrecto — aplica 16% fijo sobre todo)
protected function getOrderTaxes(Model|CashRegisterOpening $model): float
{
    $orderSubtotal = $this->getOrderSubtotal($model);
    return $orderSubtotal * 0.16; // Assuming a 16% tax rate
}

// DESPUÉS (correcto — suma el IVA real por línea)
protected function getOrderTaxes(Model|CashRegisterOpening $model): float
{
    return $this->getOrderTicketLines($model, 'open')
        ->map(fn (OrderTicketLine $line) => $line->tax)
        ->sum();
}
```

### Método 3 — `getOrderTotal` (línea \~291)

```php theme={null}
// ANTES (incorrecto — suma subtotal + IVA calculados sobre pagos)
protected function getOrderTotal(Model|CashRegisterOpening $model): float
{
    return $this->getOrderSubtotal($model) + $this->getOrderTaxes($model);
}

// DESPUÉS (correcto — suma el total real por línea)
protected function getOrderTotal(Model|CashRegisterOpening $model): float
{
    return $this->getOrderTicketLines($model, 'open')
        ->map(fn (OrderTicketLine $line) => $line->total)
        ->sum();
}
```

***

## Qué cambia y qué no cambia

| Campo del Corte Z                  | Antes                   | Después                                   |
| ---------------------------------- | ----------------------- | ----------------------------------------- |
| `#ORDER_SUBTOTAL#`                 | Pagos ÷ 1.16            | Suma de netSubtotal/1.16 por línea pagada |
| `#ORDER_TAXES#`                    | Subtotal × 16% fijo     | Suma de IVA real por línea                |
| `#ORDER_TOTAL#`                    | Subtotal + IVA estimado | Suma de total real por línea              |
| `#PAYMENT_CASH_TOTAL#`             | Sin cambio              | Sin cambio                                |
| `#PAYMENT_CARD_TOTAL_WITHOUT_TIP#` | Sin cambio              | Sin cambio                                |
| `#BALANCE_CHECK#`                  | Sin cambio              | Sin cambio                                |
| `#CASH_SHORTAGE#`                  | Sin cambio              | Sin cambio                                |
| `#THEORETICAL_FINAL_CASH#`         | Sin cambio              | Sin cambio                                |

<Note>Los campos de efectivo, tarjeta, faltante/sobrante y saldo siguen viniendo de los pagos — eso es correcto y no debe cambiar. Solo cambian los 3 campos del DESGLOSE DE VENTA.</Note>

***

## Impacto en Restaurantero Pro

Con este fix, cuando RC envíe el `POST /api/v1/ingest/sales` al cerrar el Corte Z, Restaurantero Pro recibirá los valores correctos:

* `total_sales_cents` = venta real neta (con descuentos, sin cancelados, sin voided)
* `total_sales_no_iva_cents` = venta real sin IVA calculada por línea
* Food Cost % correcto en el dashboard del dueño
* P\&L correcto
* EBITDA correcto

***

## Prompt para Cursor

```
In restaurant_pos, open the file:
app/Services/Implementations/BaseCashRegisterClosingXZOutPrinterService.php

Replace these three methods ONLY. Do not modify anything else in the file.
Do not touch any payment-related methods (getPaymentCashTotal, 
getTotalPaymentMethodNet, getPaymentCardTotal, etc.) — those are correct.

CHANGE 1 — Find getOrderSubtotal (around line 277):

Find:
    protected function getOrderSubtotal(Model|CashRegisterOpening $model): float
    {
        $totalPaymentMethodNet = $this->getTotalPaymentMethodNet($model);

        return $totalPaymentMethodNet / 1.16;
    }

Replace with:
    protected function getOrderSubtotal(Model|CashRegisterOpening $model): float
    {
        return $this->getOrderTicketLines($model, 'open')
            ->map(fn (OrderTicketLine $line) => $line->netSubtotal / 1.16)
            ->sum();
    }

CHANGE 2 — Find getOrderTaxes (around line 284):

Find:
    protected function getOrderTaxes(Model|CashRegisterOpening $model): float
    {
        $orderSubtotal = $this->getOrderSubtotal($model);

        return $orderSubtotal * 0.16; // Assuming a 16% tax rate
    }

Replace with:
    protected function getOrderTaxes(Model|CashRegisterOpening $model): float
    {
        return $this->getOrderTicketLines($model, 'open')
            ->map(fn (OrderTicketLine $line) => $line->tax)
            ->sum();
    }

CHANGE 3 — Find getOrderTotal (around line 291):

Find:
    protected function getOrderTotal(Model|CashRegisterOpening $model): float
    {
        return $this->getOrderSubtotal($model) + $this->getOrderTaxes($model);
    }

Replace with:
    protected function getOrderTotal(Model|CashRegisterOpening $model): float
    {
        return $this->getOrderTicketLines($model, 'open')
            ->map(fn (OrderTicketLine $line) => $line->total)
            ->sum();
    }

IMPORTANT:
- Only modify these 3 methods
- Do not change any other method in the file
- Do not change any payment-related methods
- Run php artisan test after the change to verify nothing breaks
```

***

## Cómo verificar que el fix funcionó

Después del cambio, en el siguiente Corte Z verifica:

1. `#ORDER_TOTAL#` coincide con la suma de todos los tickets cerrados del período
2. Si hubo una cancelación de \$500, ese monto **NO** aparece en `#ORDER_TOTAL#`
3. Si hubo un descuento del 20% en una mesa, el total refleja el precio con descuento
4. `#PAYMENT_CASH_TOTAL#` y `#PAYMENT_CARD_TOTAL_WITHOUT_TIP#` no cambiaron
5. El `cash_shortage` (faltante/sobrante) sigue calculándose correctamente

***

*Restaurant Controller — Fix Corte Z*\
*Mayo 2026 · Confidencial*
