> ## 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.

# Bitácora de Actividad — Guía para RC

> Cómo conectar Restaurant Controller a la Bitácora de Restaurantero Pro para que el dueño vea todo lo que pasa en el POS.

## Qué es la Bitácora

La **Bitácora de Actividad** es el módulo de auditoría de Restaurantero Pro. Registra cada acción de cada usuario — tanto en el dashboard como en el POS — en un solo lugar.

El dueño ve en tiempo real:

```
● Pedro (POS)       Canceló línea Ticket #4521          hace 3 min
● Marco (Dashboard) Envió OC a COTSCO por WhatsApp      hace 12 min
● Ana (POS)         Aplicó descuento 20% en Mesa 7       hace 18 min
● José (POS)        Editó precio ACEITE +5,455%          hace 2 hrs ⚠️
```

Restaurantero Pro detecta automáticamente **anomalías de comportamiento** y alerta al dueño:

* 3+ cancelaciones del mismo mesero en 2 horas
* Precio modificado fuera del rango histórico (±20%)
* Login desde dispositivo o IP nunca antes visto

***

## El endpoint

```http theme={null}
POST /api/v1/ingest/activity
X-API-Key: rpro_live_xxx
Content-Type: application/json
X-Idempotency-Key: {uuid-unico}
```

Es el mismo patrón que ya usas para el Corte Z — mismo header, mismo formato JSON:API.

***

## Qué eventos disparar desde RC

Estos son los eventos de alto valor para el dueño. Agrega los que correspondan según la versión de RC (Full Dining o Express):

### Tickets y ventas

```php theme={null}
// Cancelación de línea en ticket abierto
'event' => 'ticket.line_cancelled'
'detail' => [
    'item'         => $line->item_name,
    'reason'       => $reason,        // motivo que el mesero captura
    'amount_cents' => $line->net_total_cents * 100,
    'ticket_id'    => (string) $ticket->id,
    'authorized_by'=> $manager?->name,  // si requirió autorización
]

// Void de ticket completo
'event' => 'ticket.voided'
'detail' => [
    'ticket_total_cents' => $ticket->total * 100,
    'items_count'        => $ticket->lines->count(),
    'reason'             => $reason,
    'authorized_by'      => $manager?->name,
]

// Descuento manual aplicado
'event' => 'ticket.discount_applied'
'detail' => [
    'discount_pct'    => $discount->percentage,
    'discount_cents'  => $discount->amount * 100,
    'ticket_id'       => (string) $ticket->id,
    'discount_type'   => $discount->type, // 'percentage' | 'fixed' | 'courtesy'
]

// Precio modificado manualmente en POS
'event' => 'ticket.price_overridden'
'detail' => [
    'item'            => $line->item_name,
    'original_cents'  => $line->unit_price * 100,
    'new_cents'       => $newPrice * 100,
    'ticket_id'       => (string) $ticket->id,
]
```

### Caja y turnos

```php theme={null}
// Apertura de turno
'event' => 'cash_register.opened'
'detail' => [
    'opening_balance_cents' => $opening->initial_amount * 100,
    'shift'                 => $opening->shift, // 'morning' | 'afternoon' | 'night'
]

// Cierre con diferencia de caja
'event' => 'cash_register.closed'
'detail' => [
    'expected_cents'  => $corte->expected_cash * 100,
    'counted_cents'   => $corte->counted_cash * 100,
    'variance_cents'  => $corte->variance * 100,  // negativo = faltante
    'type'            => $corte->type, // 'xout' | 'zout'
]
```

### Usuarios y accesos

```php theme={null}
// Login exitoso
'event' => 'user.login'
'detail' => [
    'ip'     => request()->ip(),
    'device' => request()->userAgent(),
]

// Login fallido
'event' => 'user.login_failed'
'detail' => [
    'ip'             => request()->ip(),
    'attempted_user' => $request->username,
]
```

### Autorización de gerente

```php theme={null}
// Cuando el gerente autoriza cualquier acción
'event' => 'manager.override'
'detail' => [
    'action'  => 'cancel_line',  // qué se autorizó
    'for_user'=> $waiter->name,
    'ref'     => "Ticket #{$ticket->id}",
]
```

***

## Snippet PHP completo — listo para copiar

```php theme={null}
<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class RestauranteroActivityService
{
    private string $apiKey;
    private string $baseUrl = 'https://api.restauranteropro.com/api/v1';

    public function __construct()
    {
        $this->apiKey = config('restaurantero.api_key');
    }

    /**
     * Envía un evento a la Bitácora de Restaurantero Pro.
     *
     * @param string $branchIdentifier UUID del branch en RP
     * @param string $event            Tipo de evento (ticket.line_cancelled, etc.)
     * @param array  $detail           Datos específicos del evento
     * @param array  $options          actor_name, actor_role, ref_type, ref_id, device
     */
    public function log(
        string $branchIdentifier,
        string $event,
        array  $detail = [],
        array  $options = []
    ): void {
        // No bloquear el flujo del POS si RP no responde
        try {
            Http::timeout(3)
                ->withHeaders([
                    'X-API-Key'          => $this->apiKey,
                    'Content-Type'       => 'application/json',
                    'X-Idempotency-Key'  => Str::uuid()->toString(),
                ])
                ->post("{$this->baseUrl}/ingest/activity", [
                    'data' => [
                        'type'       => 'activity',
                        'attributes' => [
                            'branch_identifier' => $branchIdentifier,
                            'occurred_at'       => now()->toISOString(),
                            'actor_name'        => $options['actor_name'] ?? auth()->user()?->name ?? 'Sistema',
                            'actor_role'        => $options['actor_role'] ?? auth()->user()?->role ?? 'system',
                            'source'            => 'rc_pos',
                            'event'             => $event,
                            'ref_type'          => $options['ref_type'] ?? null,
                            'ref_id'            => $options['ref_id'] ?? null,
                            'detail'            => $detail,
                        ],
                        'meta'       => [
                            'pos_name'    => 'restaurant_controller',
                            'pos_version' => config('app.version', '2.1.0'),
                            'device'      => $options['device'] ?? request()->userAgent(),
                            'sent_at'     => now()->toISOString(),
                        ],
                    ],
                ]);
        } catch (\Throwable $e) {
            // Log local — nunca interrumpir el POS por falla de RP
            logger()->warning('RestauranteroActivity: fallo al enviar evento', [
                'event' => $event,
                'error' => $e->getMessage(),
            ]);
        }
    }
}
```

### Uso en los observers de RC

```php theme={null}
// En el observer de CancelledLine o donde manejes cancelaciones
app(RestauranteroActivityService::class)->log(
    branchIdentifier: $branch->rpro_identifier,
    event: 'ticket.line_cancelled',
    detail: [
        'item'         => $line->item_name,
        'reason'       => $reason,
        'amount_cents' => (int) round($line->net_total * 100),
    ],
    options: [
        'actor_name' => auth()->user()->name,
        'actor_role' => auth()->user()->role,
        'ref_type'   => 'ticket',
        'ref_id'     => (string) $ticket->id,
    ]
);
```

<Tip>El `try/catch` con `timeout(3)` es importante — si Restaurantero Pro tiene algún problema, el POS no se debe quedar colgado. La Bitácora es best-effort: si un evento no llega, no afecta la operación del restaurante.</Tip>

***

## Cuándo disparar cada evento

A diferencia del Corte Z (que se envía una vez al día), los eventos de Bitácora se envían **en tiempo real**, al momento en que ocurre la acción.

| Evento                    | Cuándo                                             | Síncrono/Asíncrono |
| ------------------------- | -------------------------------------------------- | ------------------ |
| `ticket.line_cancelled`   | Inmediatamente después de confirmar la cancelación | Async recomendado  |
| `ticket.voided`           | Inmediatamente después del void                    | Async recomendado  |
| `ticket.discount_applied` | Al guardar el descuento                            | Async recomendado  |
| `cash_register.opened`    | Al crear el CashRegisterOpening                    | Async recomendado  |
| `cash_register.closed`    | Al cerrar el Corte Z (antes o después del ingest)  | Async recomendado  |
| `user.login`              | En el AuthController, después de login exitoso     | Async              |
| `user.login_failed`       | En el AuthController, después de fallo             | Async              |
| `manager.override`        | Al registrar la autorización del gerente           | Síncrono OK        |

<Warning>**No envíes eventos retroactivos** — la Bitácora está diseñada para tiempo real. Si un evento falló, no lo reenvíes con `occurred_at` del pasado; simplemente déjalo pasar. El historial financiero lo cubre el Corte Z.</Warning>

***

## Checklist de integración para Bitácora

* [ ] `RestauranteroActivityService` creado y registrado en el service container
* [ ] `RESTAURANTERO_API_KEY` configurado en `.env` de RC
* [ ] `ticket.line_cancelled` disparando desde el observer de cancelaciones
* [ ] `ticket.voided` disparando desde el observer de voids
* [ ] `ticket.discount_applied` disparando al aplicar descuentos manuales
* [ ] `cash_register.opened` disparando al abrir turno
* [ ] `cash_register.closed` disparando al cerrar Corte Z
* [ ] `user.login` disparando en AuthController
* [ ] Prueba de humo: enviar evento manual con Artisan y verificar en Bitácora del dashboard
* [ ] `timeout(3)` confirmado — el POS no se cuelga si RP no responde

***

## Prueba rápida desde Artisan (RC)

Agrega este comando en RC para probar sin abrir el POS:

```php theme={null}
// En un comando Artisan temporal de RC
Http::withHeaders([
    'X-API-Key'         => env('RESTAURANTERO_API_KEY'),
    'X-Idempotency-Key' => Str::uuid(),
])->post('https://api.restauranteropro.com/api/v1/ingest/activity', [
    'data' => [
        'type'       => 'activity',
        'attributes' => [
            'branch_identifier' => '019e0c31-f390-7185-8bff-f5046c392930',
            'occurred_at'       => now()->toISOString(),
            'actor_name'        => 'Carlos Dev',
            'actor_role'        => 'developer',
            'source'            => 'rc_pos',
            'event'             => 'ticket.line_cancelled',
            'ref_type'          => 'ticket',
            'ref_id'            => '9999',
            'detail'            => [
                'item'         => 'TEST ITEM',
                'reason'       => 'Prueba de integración',
                'amount_cents' => 10000,
            ],
        ],
        'meta' => [
            'pos_name'   => 'restaurant_controller',
            'pos_version'=> '2.1.0',
            'sent_at'    => now()->toISOString(),
        ],
    ],
]);
```

Verifica en el dashboard de Restaurantero Pro → Ajustes → Bitácora que el evento aparezca con source `rc_pos`.
