# Plan Para Facturas Viejas No Saldadas Localmente Pero Cerradas En SAP

## Objetivo

Permitir que el sistema viejo siga mostrando facturas con promocion que:

- no aparecen en `ResumenProm` porque no fueron saldadas en el sistema viejo
- pero si fueron saldadas en SAP

La idea es unir dos universos:

1. Facturas que el sistema viejo ya considera saldadas
2. Facturas que el sistema viejo no considera saldadas, pero SAP si

## Recomendacion

Si, el enfoque que propones es correcto.

La forma mas limpia es:

1. Mantener la consulta actual de `ResumenProm` para lo ya saldado en el sistema viejo
2. Crear una nueva vista SQL local que traiga las facturas con promocion no saldadas en el sistema viejo
3. Consultar SAP solo por esas facturas no saldadas localmente
4. Quedarte solo con las que en SAP tengan:

```text
DocumentStatus = bost_Close
```

5. Transformarlas al mismo formato que usa `$gasto`
6. Unir ambos resultados
7. Agrupar de nuevo por factura

## Arquitectura recomendada

### Fuente 1

`ResumenProm`

Trae:

- facturas con promocion saldadas en el sistema viejo

### Fuente 2

Nueva vista SQL local, ejemplo:

`prom_facturas_promocion_no_saldadas`

Debe traer:

- facturas con promocion
- que no esten saldadas en el sistema viejo

No debe decidir si estan saldadas en SAP.
Solo debe listar las pendientes locales.

## Estructura sugerida para la nueva vista

Debe devolver al menos estas columnas:

- `VENDEDOR`
- `NOMBREV`
- `RUTA`
- `CLIENTE`
- `NOMBREC`
- `ZONA`
- `NOMBREZ`
- `FACTURA`
- `FECHA`
- `ARTICULO`
- `DESCRIPCION`
- `CANTIDAD`
- `VALOR_PROMO`
- `MONTOPROM`
- `DOC_NUM`

### Importante

`DOC_NUM` debe ser el valor que usaras para consultar SAP en:

```text
Invoices?$filter=NumAtCard eq '{{doc_num}}'
```

Si en tu caso `FACTURA` ya es realmente el `NumAtCard`, entonces no necesitas columna extra.

Si no, agrega una columna como `DOC_NUM` para evitar confusiones.

## Regla de inclusion

### Mostrar en el sistema viejo si se cumple una de estas:

1. Ya esta en `ResumenProm`
2. No esta en `ResumenProm`, pero viene en la vista de no saldadas y en SAP aparece con:

```text
DocumentStatus = bost_Close
```

## Flujo propuesto

### Paso 1

Consultar lo saldado localmente desde `ResumenProm`

### Paso 2

Consultar la nueva vista local de no saldadas

### Paso 3

De esas no saldadas, consultar en SAP una por una por `NumAtCard`

### Paso 4

Filtrar solo las que SAP devuelve con:

- `DocumentStatus = bost_Close`

### Paso 5

Mapear esas facturas al mismo formato de `$gasto`

### Paso 6

Unir:

- saldadas del viejo
- cerradas en SAP

### Paso 7

Excluir:

- clientes excluidos
- facturas ya presentes en `prom_recibo_detalle_ds`

## Consideracion importante

No recomiendo reemplazar completamente la consulta de `ResumenProm`.

Lo correcto es:

- conservar lo viejo que ya funciona
- agregar una segunda capa que complete los faltantes cerrados en SAP

Eso te reduce riesgo en el sistema viejo.

## Ejemplo de implementacion

Abajo te dejo una version base adaptada para el sistema viejo.

## Ejemplo de codigo para `datatablerecibo_entregado_prom`

```php
public function datatablerecibo_entregado_prom(Request $request)
{
    if (request()->ajax()) {
        try {
            $startDate = Carbon::parse($request->get('start_date'))->format('Ymd');
            $endDate = Carbon::parse($request->get('end_date'))->format('Ymd');
            $vendedor = $request->get('vendedor');
            $clientes = $request->get('cliente');

            // 1. Facturas que el sistema viejo ya considera saldadas
            $gastoLocal = ResumenProm::select(
                'VENDEDOR',
                'NOMBREV',
                'RUTA',
                'CLIENTE',
                'NOMBREC',
                'NOMBREZ',
                'FACTURA',
                DB::raw("CAST(FECHA AS DATE) as FECHA"),
                'FECHA as FECHA_show',
                DB::raw("SUM(MONTOPROM) as MONTOPROM")
            )
            ->whereNotIn(DB::raw('prom_resumen_ds.CLIENTE COLLATE SQL_Latin1_General_CP1_CI_AS'), function ($query) {
                $query->select(DB::raw('CLIENTE COLLATE SQL_Latin1_General_CP1_CI_AS'))
                    ->from('clientes_excluidos')
                    ->whereNull('deleted_at');
            })
            ->whereBetween('FECHA', [$startDate, $endDate])
            ->where('VENDEDOR', $vendedor)
            ->whereRaw('FACTURA NOT IN (select FACTURA COLLATE SQL_Latin1_General_CP1_CI_AI from prom_recibo_detalle_ds)');

            if (!empty($request->get('zona'))) {
                $gastoLocal = $gastoLocal->whereIn('ZONA', $request->get('zona'));
            }

            if (!empty($clientes)) {
                $gastoLocal = $gastoLocal->whereIn('CLIENTE', $clientes);
            }

            $gastoLocal = $gastoLocal
                ->groupBy('VENDEDOR', 'NOMBREV', 'RUTA', 'CLIENTE', 'NOMBREC', 'NOMBREZ', 'FACTURA', 'FECHA')
                ->get();

            // 2. Facturas con promocion no saldadas localmente
            $facturasPendientesLocal = DB::table('prom_facturas_promocion_no_saldadas')
                ->select(
                    'VENDEDOR',
                    'NOMBREV',
                    'RUTA',
                    'CLIENTE',
                    'NOMBREC',
                    'ZONA',
                    'NOMBREZ',
                    'FACTURA',
                    'FECHA',
                    'DOC_NUM',
                    DB::raw('SUM(MONTOPROM) as MONTOPROM')
                )
                ->whereNotIn(DB::raw('prom_facturas_promocion_no_saldadas.CLIENTE COLLATE SQL_Latin1_General_CP1_CI_AS'), function ($query) {
                    $query->select(DB::raw('CLIENTE COLLATE SQL_Latin1_General_CP1_CI_AS'))
                        ->from('clientes_excluidos')
                        ->whereNull('deleted_at');
                })
                ->whereBetween('FECHA', [$startDate, $endDate])
                ->where('VENDEDOR', $vendedor)
                ->whereRaw('FACTURA NOT IN (select FACTURA COLLATE SQL_Latin1_General_CP1_CI_AI from prom_recibo_detalle_ds)');

            if (!empty($request->get('zona'))) {
                $facturasPendientesLocal = $facturasPendientesLocal->whereIn('ZONA', $request->get('zona'));
            }

            if (!empty($clientes)) {
                $facturasPendientesLocal = $facturasPendientesLocal->whereIn('CLIENTE', $clientes);
            }

            $facturasPendientesLocal = $facturasPendientesLocal
                ->groupBy('VENDEDOR', 'NOMBREV', 'RUTA', 'CLIENTE', 'NOMBREC', 'ZONA', 'NOMBREZ', 'FACTURA', 'FECHA', 'DOC_NUM')
                ->get();

            // 3. Consultar SAP solo por las pendientes locales
            $facturasCerradasEnSap = collect();

            foreach ($facturasPendientesLocal as $facturaPendiente) {
                $invoiceSap = $this->consultarFacturaEnSapPorNumAtCard($facturaPendiente->DOC_NUM);

                if (empty($invoiceSap)) {
                    continue;
                }

                if (($invoiceSap['DocumentStatus'] ?? null) !== 'bost_Close') {
                    continue;
                }

                $facturasCerradasEnSap->push((object) [
                    'VENDEDOR' => $facturaPendiente->VENDEDOR,
                    'NOMBREV' => $facturaPendiente->NOMBREV,
                    'RUTA' => $facturaPendiente->RUTA,
                    'CLIENTE' => $facturaPendiente->CLIENTE,
                    'NOMBREC' => $facturaPendiente->NOMBREC,
                    'NOMBREZ' => $facturaPendiente->NOMBREZ,
                    'FACTURA' => $facturaPendiente->FACTURA,
                    'FECHA' => Carbon::parse($facturaPendiente->FECHA)->format('Y-m-d'),
                    'FECHA_show' => Carbon::parse($facturaPendiente->FECHA)->format('Y-m-d'),
                    'MONTOPROM' => (float) $facturaPendiente->MONTOPROM,
                ]);
            }

            // 4. Evitar duplicados contra lo que ya existe en ResumenProm
            $facturasLocalIds = $gastoLocal
                ->map(function ($item) {
                    return trim($item->FACTURA) . '|' . trim($item->CLIENTE);
                })
                ->flip();

            $facturasCerradasEnSap = $facturasCerradasEnSap
                ->filter(function ($item) use ($facturasLocalIds) {
                    $key = trim($item->FACTURA) . '|' . trim($item->CLIENTE);
                    return !$facturasLocalIds->has($key);
                })
                ->values();

            // 5. Unir ambos resultados
            $gasto = $gastoLocal
                ->concat($facturasCerradasEnSap)
                ->sortBy([
                    ['NOMBREV', 'asc'],
                    ['NOMBREC', 'asc'],
                    ['FACTURA', 'asc'],
                ])
                ->values();

            $start_date = Carbon::parse($request->get('start_date'))->format('Y-m-d');
            $end_date = Carbon::parse($request->get('end_date'))->format('Y-m-d');

            $vendedor_name = DB::table('dspresupuesto.dbo.Vendedor')
                ->where('codigo', $vendedor)
                ->first();

            // 6. Obtener listado de facturas detectadas como cerradas en SAP
            $facturasSapCerradas = $facturasCerradasEnSap
                ->pluck('FACTURA')
                ->filter()
                ->unique()
                ->values()
                ->all();

            // 7. Obtener desglose mezclando ResumenProm + nueva vista local
            $gasto_desgloce = $this->getFacturaDesglocePromocion($request, $facturasSapCerradas);

            return response()->json([
                'success' => true,
                'view' => view('promocion::Reportes.partials.detail_table_pagar', compact('gasto'))->render(),
                'view_print' => view('promocion::Reportes.receipt.print_disponible_pago', compact('gasto', 'start_date', 'end_date', 'vendedor_name', 'gasto_desgloce'))->render(),
                'view_print_desgloce' => view('promocion::Reportes.receipt.print_factura_desgloce', compact('start_date', 'end_date', 'vendedor_name', 'gasto_desgloce'))->render()
            ]);
        } catch (\Exception $e) {
            \Log::info($e);
            return response()->json([
                'success' => false,
                'view' => '',
                'view_print' => '',
                'msg' => 'No se pudo generar el reporte'
            ]);
        }
    }
}
```

## Como adaptar `getFacturaDesglocePromocion()`

Si el listado principal ya mezcla:

- lo que viene de `ResumenProm`
- mas las facturas no saldadas localmente pero cerradas en SAP

entonces el desglose tambien debe hacer lo mismo.

### Recomendacion

Modificar la firma del metodo para recibir un parametro adicional:

```php
public function getFacturaDesglocePromocion(Request $request, array $facturasSapCerradas = [])
```

La idea es:

1. seguir consultando `ResumenProm` para lo que si existe ahi
2. consultar la nueva vista local solo para las facturas detectadas como cerradas en SAP
3. unir ambos resultados

## Ejemplo de implementacion para `getFacturaDesglocePromocion()`

```php
public function getFacturaDesglocePromocion(Request $request, array $facturasSapCerradas = [])
{
    $gastoDesgloceLocal = ResumenProm::select(
        'VENDEDOR','NOMBREV',
        'RUTA','CLIENTE','NOMBREC',
        'NOMBREZ','FACTURA',
        DB::raw("CAST(FECHA AS DATE) as FECHA"),
        'FECHA as FECHA_show',
        'MONTOPROM',
        'ARTICULO',
        'DESCRIPCION',
        'CANTIDAD',
        'VALOR_PROMO'
    )
    ->whereNotIn(DB::raw('prom_resumen_ds.CLIENTE COLLATE SQL_Latin1_General_CP1_CI_AS'), function($query) {
        $query->select(DB::raw('CLIENTE COLLATE SQL_Latin1_General_CP1_CI_AS'))
              ->from('clientes_excluidos')
              ->whereNull('deleted_at');
    })
    ->orderByRaw('NOMBREV,NOMBREC,FACTURA');

    if (!empty($request->get('start_date')) && !empty($request->get('end_date'))) {
        $gastoDesgloceLocal = $gastoDesgloceLocal->whereBetween('FECHA', [
            Carbon::parse($request->get('start_date'))->format('Ymd'),
            Carbon::parse($request->get('end_date'))->format('Ymd')
        ]);
    }

    $gastoDesgloceLocal = $gastoDesgloceLocal->where('VENDEDOR', $request->get('vendedor'));

    if (!empty($request->get('zona'))) {
        $gastoDesgloceLocal = $gastoDesgloceLocal->whereIn('ZONA', $request->get('zona'));
    }

    if (!empty($request->get('cliente'))) {
        $gastoDesgloceLocal = $gastoDesgloceLocal->whereIn('CLIENTE', $request->get('cliente'));
    }

    $gastoDesgloceLocal = $gastoDesgloceLocal->whereRaw(
        'FACTURA NOT IN (select FACTURA COLLATE SQL_Latin1_General_CP1_CI_AI from prom_recibo_detalle_ds)'
    )->get();

    $gastoDesgloceSap = collect();

    if (!empty($facturasSapCerradas)) {
        $gastoDesgloceSap = DB::table('prom_facturas_promocion_no_saldadas')
            ->select(
                'VENDEDOR','NOMBREV',
                'RUTA','CLIENTE','NOMBREC',
                'NOMBREZ','FACTURA',
                DB::raw("CAST(FECHA AS DATE) as FECHA"),
                'FECHA as FECHA_show',
                'MONTOPROM',
                'ARTICULO',
                'DESCRIPCION',
                'CANTIDAD',
                'VALOR_PROMO'
            )
            ->whereIn('FACTURA', $facturasSapCerradas)
            ->get();
    }

    return $gastoDesgloceLocal
        ->concat($gastoDesgloceSap)
        ->unique(function ($item) {
            return trim($item->FACTURA) . '|' . trim($item->ARTICULO) . '|' . trim($item->CLIENTE);
        })
        ->values();
}
```

## Nota importante sobre `FACTURA` vs `DOC_NUM`

Puedes filtrar la nueva vista por `FACTURA` solo si ese campo coincide exactamente con el identificador que estas usando en el flujo principal.

Si el identificador real para cruzar con SAP es `DOC_NUM` o `NumAtCard`, entonces es mejor que en la nueva vista tengas esa columna y filtres por esa.

Ejemplo:

```php
->whereIn('DOC_NUM', $docNumsSapCerrados)
```

Eso depende de como modeles la nueva vista.

## Recomendacion final para el desglose

Si tu vista principal va a unir:

- facturas saldadas localmente
- facturas cerradas en SAP

entonces el desglose debe seguir exactamente la misma regla.

Si no lo haces, el usuario vera la factura en el listado principal pero no vera sus articulos en el detalle o en el Excel desglozado.

## Helper sugerido para consultar SAP por `NumAtCard`

## Imports y dependencias necesarias en el sistema viejo

Antes de pegar las funciones, asegúrate de tener estos imports en el controlador:

```php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
```

Y en tu `.env` o `config/services.php` deberías tener algo equivalente a:

```php
'sap' => [
    'base_url' => env('SAP_BASE_URL'),
    'company_db' => env('SAP_COMPANY_DB'),
    'user' => env('SAP_USER'),
    'password' => env('SAP_PASSWORD'),
],
```

Variables sugeridas en `.env`:

```text
SAP_BASE_URL=https://sap-ibero-sl.skyinone.net:50000/b1s/v2
SAP_COMPANY_DB=...
SAP_USER=...
SAP_PASSWORD=...
```

## Helpers faltantes para autenticar y consultar SAP

### `obtenerSessionSap()`

```php
private function obtenerSessionSap(): string
{
    $cacheKey = 'sap_session_id';

    if (Cache::has($cacheKey)) {
        return (string) Cache::get($cacheKey);
    }

    $baseUrl = rtrim(config('services.sap.base_url'), '/');

    $response = Http::withoutVerifying()->post($baseUrl . '/Login', [
        'CompanyDB' => config('services.sap.company_db'),
        'Password' => config('services.sap.password'),
        'UserName' => config('services.sap.user'),
        'Language' => 25,
    ]);

    if (!$response->successful()) {
        Log::error('Error iniciando sesion en SAP', [
            'status' => $response->status(),
            'body' => $response->body(),
        ]);

        throw new \RuntimeException('No se pudo autenticar con SAP Service Layer');
    }

    $sessionId = (string) $response->json('SessionId');
    $timeout = (int) $response->json('SessionTimeout', 30);

    if ($sessionId === '') {
        throw new \RuntimeException('SAP no devolvio SessionId');
    }

    Cache::put($cacheKey, $sessionId, now()->addMinutes(max($timeout - 2, 1)));

    return $sessionId;
}
```

### `sapGetRequest()`

Esto te evita repetir el manejo del `B1SESSION` y hace relogin si SAP devuelve `401`.

```php
private function sapGetRequest(string $endpoint, array $query = [])
{
    $baseUrl = rtrim(config('services.sap.base_url'), '/');
    $host = parse_url($baseUrl, PHP_URL_HOST) ?: 'localhost';

    $sessionId = $this->obtenerSessionSap();

    $response = Http::withoutVerifying()
        ->withCookies(['B1SESSION' => $sessionId], $host)
        ->get($baseUrl . $endpoint, $query);

    if ($response->status() === 401) {
        Cache::forget('sap_session_id');
        $sessionId = $this->obtenerSessionSap();

        $response = Http::withoutVerifying()
            ->withCookies(['B1SESSION' => $sessionId], $host)
            ->get($baseUrl . $endpoint, $query);
    }

    return $response;
}
```

### `sapGetAbsoluteUrl()`

Este helper sirve si luego decides seguir `@odata.nextLink`.

```php
private function sapGetAbsoluteUrl(string $url)
{
    $baseUrl = rtrim(config('services.sap.base_url'), '/');
    $host = parse_url($baseUrl, PHP_URL_HOST) ?: 'localhost';

    $sessionId = $this->obtenerSessionSap();

    $response = Http::withoutVerifying()
        ->withCookies(['B1SESSION' => $sessionId], $host)
        ->get($url);

    if ($response->status() === 401) {
        Cache::forget('sap_session_id');
        $sessionId = $this->obtenerSessionSap();

        $response = Http::withoutVerifying()
            ->withCookies(['B1SESSION' => $sessionId], $host)
            ->get($url);
    }

    return $response;
}
```

```php
private function consultarFacturaEnSapPorNumAtCard(string $docNum)
{
    $response = $this->sapGetRequest('/Invoices', [
            '$filter' => "NumAtCard eq '{$docNum}'",
            '$select' => 'DocEntry,DocNum,CardCode,CardName,DocDate,DocTotal,NumAtCard,DocumentStatus,SalesPersonCode'
        ]);

    if (!$response->successful()) {
        \Log::info('Error consultando invoice en SAP', [
            'doc_num' => $docNum,
            'status' => $response->status(),
            'body' => $response->body(),
        ]);
        return null;
    }

    $data = $response->json('value', []);

    return collect($data)->first();
}
```

## Helper opcional para consultar varias facturas

Si quieres encapsular la logica de revisar muchas facturas pendientes locales:

```php
private function obtenerFacturasCerradasEnSap($facturasPendientesLocal)
{
    $facturasCerradasEnSap = collect();

    foreach ($facturasPendientesLocal as $facturaPendiente) {
        $invoiceSap = $this->consultarFacturaEnSapPorNumAtCard($facturaPendiente->FACTURA);

        if (empty($invoiceSap)) {
            continue;
        }

        if (($invoiceSap['DocumentStatus'] ?? null) !== 'bost_Close') {
            continue;
        }

        $facturasCerradasEnSap->push((object) [
            'VENDEDOR' => $facturaPendiente->VENDEDOR,
            'NOMBREV' => $facturaPendiente->NOMBREV,
            'RUTA' => $facturaPendiente->RUTA,
            'CLIENTE' => $facturaPendiente->CLIENTE,
            'NOMBREC' => $facturaPendiente->NOMBREC,
            'NOMBREZ' => $facturaPendiente->NOMBREZ,
            'FACTURA' => $facturaPendiente->FACTURA,
            'FECHA' => Carbon::parse($facturaPendiente->FECHA)->format('Y-m-d'),
            'FECHA_show' => Carbon::parse($facturaPendiente->FECHA)->format('Y-m-d'),
            'MONTOPROM' => (float) $facturaPendiente->MONTOPROM,
            'DOC_ENTRY_SAP' => $invoiceSap['DocEntry'] ?? null,
            'DOC_NUM_SAP' => $invoiceSap['DocNum'] ?? null,
            'DOCUMENT_STATUS_SAP' => $invoiceSap['DocumentStatus'] ?? null,
        ]);
    }

    return $facturasCerradasEnSap;
}
```

## Nota sobre rendimiento

El enfoque anterior funciona, pero hace una llamada SAP por factura.

Si el cliente todavía tiene muchas pendientes, lo recomendable a mediano plazo es:

1. crear una tabla local de conciliacion
2. poblarla con proceso batch
3. consultar la tabla local desde el sistema viejo

Para salir del paso, este enfoque de helpers funciona bien.

## Advertencia importante de rendimiento

Si haces una llamada SAP por cada factura, puede ponerse lento si salen muchas.

### Mejoras posibles

1. Hacerlo solo para las facturas pendientes locales del vendedor y rango seleccionado
2. Limitar por pagina o por rango corto
3. Guardar cache temporal del resultado SAP
4. Si el volumen es alto, crear una sincronizacion local del estado de invoices SAP

## Recomendacion operativa final

### Solucion rapida

Sirve esta estrategia:

- vista local de pendientes
- consulta SAP por `NumAtCard`
- unir con `ResumenProm`

### Solucion mas robusta

Crear una tabla de conciliacion local, por ejemplo:

`prom_facturas_estado_sap`

con:

- `factura`
- `doc_num`
- `cliente`
- `document_status`
- `doc_entry`
- `doc_total`
- `sales_person_code`
- `sincronizado_en`

Y refrescarla por lote.

## Mi opinion final

Tu idea es buena y es la forma correcta de resolver la transicion sin romper el sistema viejo.

La unica recomendacion fuerte es:

- no reemplazar `ResumenProm`
- sino complementarlo con el universo de pendientes locales cerrados en SAP

Asi mantienes compatibilidad con lo que ya funciona y solo rellenas el hueco de las facturas que se cerraron fuera del sistema viejo.

## Otros lugares donde aplicar el mismo cambio

Una vez que `datatablerecibo_entregado_prom()` ya funciona con:

- `ResumenProm`
- mas `prom_facturas_promocion_no_saldadas` validadas en SAP

lo recomendable es extender la misma idea a los otros puntos donde aun se usa `ResumenProm`.

### 1. `getFacturaDesglocePromocion(Request $request, array $facturasSapCerradas = [])`

Este ya lo vimos arriba.

Debe unir:

- desglose que existe en `ResumenProm`
- desglose que venga de `prom_facturas_promocion_no_saldadas`

filtrado solo por las facturas identificadas como cerradas en SAP.

### 2. `datatableResumenPromociones(Request $request)`

Actualmente arma un resumen por factura usando `ResumenProm`.

Debe adaptarse igual que `datatablerecibo_entregado_prom()`:

1. obtener saldadas locales desde `ResumenProm`
2. obtener no saldadas locales desde `prom_facturas_promocion_no_saldadas`
3. consultar SAP por `NumAtCard`
4. quedarte con `DocumentStatus = bost_Close`
5. unir ambos resultados
6. agrupar por:

- `VENDEDOR`
- `NOMBREV`
- `RUTA`
- `CLIENTE`
- `NOMBREC`
- `NOMBREZ`
- `FACTURA`
- `FECHA`

Si esa pantalla es un resumen por factura, la lógica debe ser la misma que ya aplicaste.

### 3. `datatablegenerar_pago(Request $request)`

Esta funcion tambien usaba `ResumenProm` para facturas pagables.

Debe migrarse a la misma regla:

- traer lo saldado localmente
- completar con las no saldadas localmente pero cerradas en SAP

La salida final debe mantener el mismo formato actual:

- `VENDEDOR`
- `NOMBREV`
- `RUTA`
- `CLIENTE`
- `NOMBREC`
- `NOMBREZ`
- `FACTURA`
- `FECHA`
- `MONTOPROM`

### 4. `show($id)`

Este metodo hoy busca el detalle de una factura puntual en `ResumenProm`.

Si la factura mostrada puede venir del conjunto "cerrada en SAP pero no existente en ResumenProm", entonces debes cambiarlo.

La estrategia recomendada:

1. intentar buscar en `ResumenProm`
2. si no aparece nada:
   - buscar en `prom_facturas_promocion_no_saldadas`
   - validar que esa factura este entre las identificadas como cerradas en SAP
3. devolver el mismo formato:

- `CANTIDAD`
- `DESCRIPCION`
- `FACTURA`
- `MONTOPROM`

### 5. `obtener_recibo_creado($id_recibo)`

Aqui el sistema arma el detalle del recibo leyendo las facturas del recibo y luego buscando sus lineas en `ResumenProm`.

Si algun recibo termina incluyendo facturas que fueron cerradas en SAP pero no estaban en `ResumenProm`, entonces debes hacer lo mismo:

1. obtener las facturas del recibo
2. buscar en `ResumenProm`
3. buscar las faltantes en `prom_facturas_promocion_no_saldadas`
4. unir resultados

### 6. `generar_recibo(Request $request)`

Esta funcion tambien usa `ResumenProm` para construir la impresion final del recibo despues de crearlo.

Debe cambiar de la misma forma que `obtener_recibo_creado()`:

- consultar el detalle local en `ResumenProm`
- completar las facturas faltantes desde la nueva vista de no saldadas

### 7. `show_recibo($id)`

Misma logica que los dos puntos anteriores.

Si el recibo contiene facturas que solo fueron detectadas como cerradas en SAP, el detalle impreso no puede depender solo de `ResumenProm`.

## Regla general para todos los usos de `ResumenProm`

Cada vez que hoy exista una consulta como esta:

```php
$gasto = ResumenProm::...
```

debes preguntarte:

### Esa pantalla o impresion:

1. ¿trabaja a nivel resumen por factura?
2. ¿trabaja a nivel desglose por articulo?
3. ¿trabaja con una lista de facturas ya seleccionadas?

Segun el caso:

- si es resumen:
  unir saldadas locales + cerradas en SAP
- si es desglose:
  unir detalle local + detalle desde `prom_facturas_promocion_no_saldadas`
- si es recibo ya creado:
  usar las facturas del recibo como lista base y luego consultar ambos orígenes

## Patrón recomendado de reutilización

Para no repetir mucha lógica en el sistema viejo, te recomiendo crear helpers como estos:

### `obtenerFacturasPromocionCerradas(Request $request)`

Devuelve el resumen unificado:

- local desde `ResumenProm`
- mas pendientes locales cerradas en SAP

### `obtenerDesgloseFacturasPromocion(Request $request, array $facturasSapCerradas = [], array $facturasObjetivo = [])`

Devuelve el detalle unificado:

- local desde `ResumenProm`
- mas detalle desde `prom_facturas_promocion_no_saldadas`

### `obtenerFacturasCerradasEnSap($facturasPendientesLocal)`

Ya te lo deje arriba como helper sugerido.

Con eso puedes reutilizar la misma logica en varias funciones sin copiar todo.

## Orden recomendado para implementarlo

Para no romper mucho el sistema viejo, yo lo haria en este orden:

1. `datatablerecibo_entregado_prom()`
   Esto ya lo resolviste.

2. `getFacturaDesglocePromocion()`
   Porque es el complemento directo del paso 1.

3. `datatablegenerar_pago()`
   Porque usa el mismo tipo de resumen por factura.

4. `show($id)`
   Para que el detalle por factura no falle en las facturas cerradas en SAP.

5. `obtener_recibo_creado()`
6. `generar_recibo()`
7. `show_recibo()`

Estos tres ultimos deben quedar alineados entre si.

8. `datatableResumenPromociones()`
   Si todavia se usa operativamente.

## Nota final importante

No todos los usos de `ResumenProm` necesitan consultar SAP en tiempo real por separado si ya vienes del flujo unificado.

Ejemplo:

- si ya en `datatablerecibo_entregado_prom()` detectaste que ciertas facturas estaban cerradas en SAP
- entonces al llamar el desglose puedes pasarles directamente esas facturas

Eso evita repetir validaciones innecesarias.

La idea general es:

- detectar una sola vez las facturas cerradas en SAP
- reutilizar esa lista en el resto del flujo
