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

# API de Integración v1.0.3

> Documentación completa de la API de Restaurantero Pro para integraciones POS

# API de Integración — Restaurantero Pro

**Versión:** 1.0.3\
**Base URL:** `https://my.restauranteropro.com/api/v1`\
**Formato:** JSON (inspirado en JSON:API)\
**Autenticación:** Header `X-API-Key`

***

## 1. Autenticación

Todos los endpoints requieren el header:

```http theme={null}
X-API-Key: rpro_live_XXXXXXXXXXXXXXXX
```

Las API Keys se generan en el panel de Restaurantero Pro por sucursal.
Usa `rpro_test_` durante las pruebas y `rpro_live_` en producción.

### Errores de autenticación

| Código                  | Significado                                        | Qué hacer                      |
| ----------------------- | -------------------------------------------------- | ------------------------------ |
| `401 Unauthorized`      | API Key faltante o malformada                      | Verifica el header `X-API-Key` |
| `403 Forbidden`         | API Key válida pero sin permiso para este endpoint | Verifica el plan contratado    |
| `429 Too Many Requests` | Rate limit excedido                                | Espera `Retry-After` segundos  |

***

## 2. Convenciones generales

### Formato de requests y responses

Todos los requests y responses usan **JSON** con estructura JSON:API:

```json theme={null}
{
  "data": {
    "type": "sales",
    "attributes": {
      "branch_identifier": "uuid-aqui",
      "date": "2026-05-15",
      "total_sales_cents": 534550
    },
    "meta": {
      "pos_name": "restaurant_controller",
      "pos_version": "3.2.1",
      "sent_at": "2026-05-15T23:45:00Z"
    }
  }
}
```

### Dinero — siempre en centavos

<Warning>Todos los campos monetarios son enteros que representan centavos. Nunca se envían decimales.</Warning>

```
$150.00 MXN  →  15000
$534.50 MXN  →  53450
$1,200.00 MXN → 120000
```

### Fechas y horas

* Fechas: `YYYY-MM-DD`
* Timestamps: ISO 8601 con timezone: `2026-05-15T23:45:00-07:00`
* Hermosillo (sin cambio de horario): usar `-07:00` todo el año

### Idempotencia — obligatoria en todos los POST de ingesta

Todos los `POST /api/v1/ingest/*` requieren el header `X-Idempotency-Key`.

```
Intento 1:  Inmediato
Intento 2:  5 segundos después
Intento 3:  30 segundos después
Intento 4:  5 minutos después
Intento 5:  Marcar como pendiente y reintentar al arrancar RC
```

<Note>Para RC: Usa el `identifier` UUID del `CashRegisterOpening` como Idempotency Key.</Note>

***

## 3. Endpoints de Ingesta

<Note>Tu POS implementa estos endpoints. Tu sistema llama a estos endpoints para enviarnos datos. Nosotros los procesamos y generamos la inteligencia.</Note>

### Orden correcto al cerrar un Corte Z

```bash theme={null}
# SETUP — una sola vez al iniciar la integración
0a. POST /api/v1/ingest/categories     # categorías de Bakery Pro (PRIMERO si usa Bakery)
0b. POST /api/v1/ingest/catalog        # catálogo de platillos

# AL CERRAR EL CORTE Z — cada noche
1. POST /api/v1/ingest/orders          # líneas de lo que se vendió
2. POST /api/v1/ingest/discounts       # descuentos del turno
3. POST /api/v1/ingest/cancellations   # cancelaciones y voids
4. POST /api/v1/ingest/payments        # cómo se pagó
5. POST /api/v1/ingest/sales           # resumen de ventas
6. POST /api/v1/ingest/cash-register   # el corte (dispara el procesamiento)
```

***

### 3.1 Corte de caja — `POST /api/v1/ingest/cash-register`

Registra un corte de caja. Tipos:

* **Corte X** (`xout`): cierre de turno parcial — informativo
* **Corte Z** (`zout`): cierre del día — trigger principal que procesa todos los cubos

**Body del request:**

```json theme={null}
{
  "data": {
    "type": "cash-register",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "closing_type": "zout",
      "start_at": "2026-05-15T06:00:00-07:00",
      "end_at": "2026-05-15T23:45:00-07:00",
      "status": "completed",
      "summaries": [
        { "type": "cash_sales", "name": "Efectivo", "amount_cents": 125000 },
        { "type": "card_sales", "name": "Tarjeta", "amount_cents": 409550 },
        { "type": "tips_received", "name": "Propinas", "amount_cents": 30000 },
        { "type": "cash_variance", "name": "Diferencia", "amount_cents": -500 }
      ]
    },
    "meta": {
      "pos_name": "restaurant_controller",
      "pos_version": "3.2.1",
      "pos_variant": "full_dining",
      "sent_at": "2026-05-15T23:46:00Z",
      "cashier_name": "Ana García"
    }
  }
}
```

**Response exitoso `202 Accepted`:**

```json theme={null}
{
  "data": {
    "type": "cash-register-ingest",
    "attributes": {
      "queued": true,
      "message": "Corte Z recibido. Los cubos se regenerarán en los próximos segundos."
    }
  }
}
```

<Note>La respuesta es `202 Accepted` — el procesamiento es asíncrono. Usa webhooks (`snapshot.completed`) para saber cuándo los datos están listos.</Note>

***

### 3.2 Ventas — `POST /api/v1/ingest/sales`

**Body del request:**

```json theme={null}
{
  "data": {
    "type": "sales",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "date": "2026-05-15",
      "shift": "dinner",
      "closing_type": "zout",
      "total_sales_cents": 534550,
      "total_sales_no_iva_cents": 461681,
      "food_sales_cents": 366614,
      "beverage_sales_cents": 167936,
      "bar_sales_cents": 0,
      "covers": 87,
      "tables_served": 24,
      "avg_ticket_cents": 6144
    },
    "meta": {
      "pos_name": "restaurant_controller",
      "sent_at": "2026-05-15T23:46:00Z"
    }
  }
}
```

***

### 3.3 Pagos por método — `POST /api/v1/ingest/payments`

```json theme={null}
{
  "data": {
    "type": "payments",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "date": "2026-05-15",
      "closing_type": "zout",
      "payments": [
        {
          "payment_type": "cash",
          "payment_type_name": "Efectivo",
          "amount_cents": 125000,
          "tips_cents": 8000,
          "change_cents": 3500,
          "transaction_count": 18
        },
        {
          "payment_type": "card_credit",
          "payment_type_name": "Tarjeta Crédito",
          "amount_cents": 280000,
          "tips_cents": 15000,
          "change_cents": 0,
          "transaction_count": 31
        }
      ]
    }
  }
}
```

**Tipos de pago estándar:**

| payment\_type | Descripción          |
| ------------- | -------------------- |
| `cash`        | Efectivo             |
| `card_credit` | Tarjeta crédito      |
| `card_debit`  | Tarjeta débito       |
| `transfer`    | Transferencia / SPEI |
| `rappi`       | Rappi                |
| `uber_eats`   | Uber Eats            |
| `didi_food`   | DiDi Food            |
| `other`       | Otro                 |

***

### 3.4 Líneas de orden — `POST /api/v1/ingest/orders`

Envía los productos vendidos. **Cada línea genera una fila independiente** para Top 10 productos, Star Employees y Ventas por categoría.

```json theme={null}
{
  "data": {
    "type": "orders",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "date": "2026-05-15",
      "closing_type": "zout",
      "lines": [
        {
          "item_identifier": "550e8400-e29b-41d4-a716-446655440001",
          "item_sku": "010501",
          "name": "Aguachile Mar y Tierra",
          "category": "Mariscos",
          "category_type": "food",
          "quantity": 2,
          "unit_price_cents": 28500,
          "subtotal_cents": 57000,
          "discount_cents": 0,
          "net_total_cents": 57000,
          "waiter_name": "Carlos López",
          "ordered_at": "2026-05-15T20:15:00-07:00"
        }
      ]
    }
  }
}
```

***

### 3.5 Catálogo de platillos — `POST /api/v1/ingest/catalog`

Sincroniza el catálogo completo del POS con Restaurantero Pro. Usa **upsert** por `item_identifier` o `item_sku`.

<Note>Este endpoint se llama **una vez al inicio** de la integración y luego cada vez que cambie el catálogo. La forma recomendada es un `ItemObserver` en RC que dispare el envío automáticamente.</Note>

<Note>**Integración Bakery Pro:** Sincroniza las categorías PRIMERO usando `POST /api/v1/ingest/categories` (sección 3.6) antes de enviar productos. RC Express necesita las categorías para generar el `identification` correctamente.</Note>

**Cuándo enviar el catálogo:**

* Al configurar la integración por primera vez
* Cuando se crea un `Item` nuevo en RC
* Cuando se modifica un `Item` (precio, nombre, categoría)
* Cuando se desactiva un `Item`

**Body del request:**

```json theme={null}
{
  "data": {
    "type": "catalog",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "items": [
        {
          "item_identifier": "550e8400-e29b-41d4-a716-446655440001",
          "item_sku": "010501",
          "name": "BOLLO ALEMÁN",
          "description": "Bollo de mantequilla",
          "category": "BOLLITOS",
          "category_type": "food",
          "type": "simple",
          "unit_price_cents": 2800,
          "unit_cost_cents": 1200,
          "status": "active"
        }
      ]
    },
    "meta": {
      "pos_name": "restaurant_controller",
      "sent_at": "2026-06-25T10:00:00Z"
    }
  }
}
```

**Campos por item:**

| Campo              | Tipo    | Requerido   | Descripción                                                  |
| ------------------ | ------- | ----------- | ------------------------------------------------------------ |
| `name`             | string  | ✅ Sí        | Nombre del platillo o producto                               |
| `item_identifier`  | uuid    | Recomendado | UUID del item en RC — clave de upsert principal              |
| `item_sku`         | string  | Recomendado | `identification` del item en RC — clave de upsert secundaria |
| `category`         | string  | No          | Nombre de la categoría                                       |
| `category_type`    | string  | No          | `food`, `beverage`, `bar` o `other`                          |
| `type`             | string  | No          | Tipo del item (`simple`, `combo`, etc.)                      |
| `unit_price_cents` | integer | No          | Precio de venta en centavos                                  |
| `unit_cost_cents`  | integer | No          | Costo del platillo en centavos                               |
| `description`      | string  | No          | Descripción del platillo                                     |
| `status`           | string  | No          | `active` o `inactive` (default: `active`)                    |

<Warning>Si envías 5 o más items, los platillos que no vengan en el payload serán marcados automáticamente como `inactive`.</Warning>

**Response exitoso `200 OK`:**

```json theme={null}
{
  "data": {
    "type": "catalog-ingest",
    "attributes": {
      "created": 1,
      "updated": 0,
      "total_sent": 1,
      "message": "Catálogo sincronizado: 1 nuevo, 0 actualizados.",
      "items": [
        {
          "item_identifier": "550e8400-e29b-41d4-a716-446655440001",
          "identification": "010501",
          "name": "BOLLO ALEMÁN"
        }
      ]
    }
  }
}
```

<Warning>**Bakery Pro requiere el campo `identification` en el response.** Por cada item creado, RC Express debe incluir el `identification` generado por `makeIdentificationFromCategoryHierarchy()`. Bakery Pro lo almacena como SKU visible del producto en reportes y etiquetas.</Warning>

**Mapeo desde RC para Carlos:**

```php theme={null}
// En RestauranteroProService.php — método sendCatalog()
$items = Item::with(['category', 'unitPrice', 'unitCost'])
    ->where('status', 'active')
    ->get()
    ->map(fn($item) => [
        'item_identifier'  => $item->identifier,
        'item_sku'         => $item->identification,
        'name'             => $item->name,
        'description'      => $item->description,
        'category'         => $item->category?->name,
        'category_type'    => $this->mapCategoryType($item->category),
        'type'             => $item->type,
        'unit_price_cents' => $item->unitPrice
            ? (int) ($item->unitPrice->amount * 100)
            : 0,
        'unit_cost_cents'  => $item->unitCost
            ? (int) ($item->unitCost->amount * 100)
            : 0,
        'status' => $item->status === 'active' ? 'active' : 'inactive',
    ])->toArray();
```

<Tip>El precio en RC viene del trait `Amountable` — usa `$item->unitPrice->amount` (tipo `unit_price`) y `$item->unitCost->amount` (tipo `unit_cost`). Multiplica por 100 para convertir a centavos.</Tip>

***

### 3.6 Categorías Bakery Pro — `POST /api/v1/ingest/categories`

Sincroniza las categorías del catálogo de Bakery Pro hacia RC Express.

<Warning>**Sincronizar categorías SIEMPRE antes que productos.** RC Express necesita el `category_id` interno para calcular el `identification` del item. Si se sincronizan productos antes que categorías, el `identification` generado será incorrecto.</Warning>

**Cuándo enviar categorías:**

* Al configurar la integración por primera vez
* Cuando se crea una categoría nueva en Bakery Pro
* Cuando se renombra una categoría en Bakery Pro

**Body del request:**

```json theme={null}
{
  "data": {
    "type": "categories",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "categories": [
        {
          "identifier": "uuid-categoria-bakery-001",
          "name": "BOLLITOS",
          "parent_identifier": null,
          "sequence": 1
        },
        {
          "identifier": "uuid-subcategoria-bakery-002",
          "name": "Bollitos Especiales",
          "parent_identifier": "uuid-categoria-bakery-001",
          "sequence": 2
        }
      ]
    }
  }
}
```

**Campos por categoría:**

| Campo               | Tipo    | Requerido | Descripción                                             |
| ------------------- | ------- | --------- | ------------------------------------------------------- |
| `identifier`        | uuid    | ✅ Sí      | UUID de la categoría en Bakery Pro — clave de upsert    |
| `name`              | string  | ✅ Sí      | Nombre de la categoría                                  |
| `parent_identifier` | uuid    | No        | UUID de la categoría padre. `null` si es categoría raíz |
| `sequence`          | integer | No        | Orden de aparición en el POS                            |

**Response exitoso `200 OK`:**

```json theme={null}
{
  "data": {
    "type": "categories-ingest",
    "attributes": {
      "created": 2,
      "updated": 0,
      "categories": [
        {
          "identifier": "uuid-categoria-bakery-001",
          "rc_express_id": 5,
          "name": "BOLLITOS"
        },
        {
          "identifier": "uuid-subcategoria-bakery-002",
          "rc_express_id": 6,
          "name": "Bollitos Especiales"
        }
      ]
    }
  }
}
```

<Warning>RC Express debe devolver el `rc_express_id` de cada categoría en el response. Bakery Pro lo necesita para mapear correctamente las categorías al sincronizar productos.</Warning>

**Mapeo desde Bakery Pro:**

```php theme={null}
// En CatalogSyncService.php — método pushCategoriesToRCExpress()
$categories = FinishedGoodsCategory::where('group_id', $branch->group_id)
    ->orderBy('parent_id')  // raíces primero, hijos después
    ->get()
    ->map(fn($cat) => [
        'identifier'        => $cat->identifier,
        'name'              => $cat->name,
        'parent_identifier' => $cat->parent?->identifier,
        'sequence'          => $cat->sequence ?? 0,
    ])->toArray();
```

***

### 3.7 Descuentos — `POST /api/v1/ingest/discounts`

```json theme={null}
{
  "data": {
    "type": "discounts",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "date": "2026-05-15",
      "closing_type": "zout",
      "discounts": [
        {
          "item_identifier": "550e8400-e29b-41d4-a716-446655440001",
          "item_sku": "010501",
          "item_name": "BOLLO ALEMÁN",
          "quantity": 1,
          "amount_cents": 500,
          "reason": "Cortesía del chef",
          "authorized_by": "Marco Olivas",
          "discounted_at": "2026-05-15T20:30:00-07:00"
        }
      ]
    }
  }
}
```

***

### 3.8 Cancelaciones — `POST /api/v1/ingest/cancellations`

```json theme={null}
{
  "data": {
    "type": "cancellations",
    "attributes": {
      "branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "date": "2026-05-15",
      "closing_type": "zout",
      "cancellations": [
        {
          "item_identifier": "550e8400-e29b-41d4-a716-446655440001",
          "item_sku": "010501",
          "item_name": "BOLLO ALEMÁN",
          "quantity": 1,
          "amount_cents": 2800,
          "reason": "Error de captura",
          "cancelled_by": "Ana García",
          "authorized_by": "Marco Olivas",
          "cancelled_at": "2026-05-15T20:35:00-07:00"
        }
      ]
    }
  }
}
```

***

## 4. Catálogo de errores

| Código HTTP | Código interno       | Causa                        | Solución                                                               |
| ----------- | -------------------- | ---------------------------- | ---------------------------------------------------------------------- |
| `400`       | `RPRO_ERR_400`       | JSON malformado              | Verifica estructura `data.type`, `data.attributes`                     |
| `401`       | `RPRO_ERR_401`       | API Key faltante o inválida  | Verifica el header `X-API-Key`                                         |
| `403`       | `RPRO_ERR_403`       | Sin permisos                 | Verifica el plan contratado                                            |
| `404`       | `RPRO_ERR_404`       | Branch no encontrado         | Verifica el UUID del branch                                            |
| `409`       | `RPRO_ERR_409`       | Idempotency key ya procesada | No reintentar — ya está guardado                                       |
| `422`       | `RPRO_ERR_422`       | Campo requerido faltante     | Lee el campo `error.field`                                             |
| `422`       | `RPRO_ERR_422_CENTS` | Monto no es entero           | Multiplica por 100 y convierte a `int`                                 |
| `429`       | `RPRO_ERR_429`       | Rate limit excedido          | Espera `Retry-After` segundos                                          |
| `500`       | `RPRO_ERR_500`       | Error interno                | Reportar a [dev@restauranteropro.com](mailto:dev@restauranteropro.com) |

***

## 5. Checklist de integración

* [ ] **1. API Key configurada** — `RESTAURANTERO_API_KEY` en `.env` con prefijo `rpro_live_`
* [ ] **2. Branch Identifier correcto** — UUID de la sucursal en `RESTAURANTERO_BRANCH_IDENTIFIER`
* [ ] **3. Categorías enviadas (Bakery Pro)** — `POST /ingest/categories` con todas las categorías activas ANTES de enviar productos
* [ ] **4. Catálogo enviado** — `POST /ingest/catalog` con todos los platillos activos al iniciar la integración
* [ ] **5. `identification` guardado** — RC Express devuelve `identification` por item creado; Bakery Pro lo almacena como SKU
* [ ] **6. ItemObserver configurado** — El catálogo se actualiza automáticamente cuando cambia un Item en RC
* [ ] **7. Corte Z detectado** — El service se invoca cuando `CashRegisterOpening` es tipo `zout` y status `completed`
* [ ] **8. Corte X no envía datos de negocio** — Solo `POST /ingest/cash-register` con `closing_type: "xout"`
* [ ] **9. Idempotency Keys únicos** — Se usa el `identifier` del `CashRegisterOpening` como base con sufijos
* [ ] **10. Montos en centavos** — Todos los `*_cents` son integers, no decimales
* [ ] **11. `waiter_name` incluido en cada línea** — Requerido para Star Employees
* [ ] **12. `category_type` correcto** — `food`, `beverage`, `bar` o `other` por línea
* [ ] **13. Retry implementado** — Al menos 3 intentos con backoff exponencial
* [ ] **14. Logging activo** — Cada POST exitoso y fallido queda en los logs de RC
* [ ] **15. Orden de envío correcto** — orders → discounts → cancellations → payments → sales → cash-register
* [ ] **16. Prueba con key `rpro_test_`** — Enviar categorías + catálogo + 3 cortes Z de prueba

***

## 6. Flujo completo de integración

### 6.1 Flujo diario (restaurant\_controller)

```
AL INICIAR LA INTEGRACIÓN
──────────────────────────
0a. POST /api/v1/ingest/categories     ← categorías Bakery Pro (si aplica)
0b. POST /api/v1/ingest/catalog        ← catálogo completo de platillos

DURANTE EL SERVICIO
─────────────────────
- GET /api/v1/live/sales/today         ← ventas del turno en tiempo real
- GET /api/v1/live/alerts/active       ← alertas del turno

AL CERRAR EL CORTE Z
─────────────────────
1. POST /api/v1/ingest/orders
2. POST /api/v1/ingest/discounts
3. POST /api/v1/ingest/cancellations
4. POST /api/v1/ingest/payments
5. POST /api/v1/ingest/sales
6. POST /api/v1/ingest/cash-register   ← dispara procesamiento → webhook snapshot.completed

AL DÍA SIGUIENTE
─────────────────
- GET /api/v1/intelligence/pnl
- GET /api/v1/intelligence/kpis
- GET /api/v1/intelligence/alerts
```

### 6.2 Snippet PHP completo

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

namespace App\Services;

use App\Models\CashRegisterOpening;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class RestauranteroProService
{
    private string $baseUrl;
    private string $apiKey;
    private string $branchIdentifier;

    public function __construct()
    {
        $this->baseUrl          = config('restaurantero.base_url', 'https://my.restauranteropro.com/api/v1');
        $this->apiKey           = config('restaurantero.api_key');
        $this->branchIdentifier = config('restaurantero.branch_identifier');
    }

    public function sendZOut(CashRegisterOpening $closing): void
    {
        $closingId = $closing->identifier;

        $this->post('/ingest/orders',        $this->buildOrdersPayload($closing),       $closingId . '-orders');
        $this->post('/ingest/discounts',     $this->buildDiscountsPayload($closing),    $closingId . '-discounts');
        $this->post('/ingest/cancellations', $this->buildCancellationsPayload($closing),$closingId . '-cancellations');
        $this->post('/ingest/payments',      $this->buildPaymentsPayload($closing),     $closingId . '-payments');
        $this->post('/ingest/sales',         $this->buildSalesPayload($closing),        $closingId . '-sales');
        $this->post('/ingest/cash-register', $this->buildCashRegisterPayload($closing), $closingId);
    }

    public function sendCatalog(array $items): void
    {
        $this->post('/ingest/catalog', [
            'data' => [
                'type' => 'catalog',
                'attributes' => [
                    'branch_identifier' => $this->branchIdentifier,
                    'items' => $items,
                ],
                'meta' => [
                    'pos_name' => 'restaurant_controller',
                    'sent_at'  => now()->toIso8601String(),
                ],
            ],
        ], 'catalog-' . now()->format('Y-m-d'));
    }

    public function sendCategories(array $categories): array
    {
        // Devuelve el response con rc_express_id por categoría
        return $this->postWithResponse('/ingest/categories', [
            'data' => [
                'type' => 'categories',
                'attributes' => [
                    'branch_identifier' => $this->branchIdentifier,
                    'categories'        => $categories,
                ],
            ],
        ], 'categories-' . now()->format('Y-m-d'));
    }

    private function post(string $endpoint, array $payload, string $idempotencyKey): void
    {
        $attempts = 0;
        $delays   = [0, 5, 30];

        while ($attempts < 3) {
            try {
                $response = Http::withHeaders([
                    'X-API-Key'         => $this->apiKey,
                    'X-Idempotency-Key' => $idempotencyKey,
                    'Content-Type'      => 'application/json',
                ])->timeout(15)->post($this->baseUrl . $endpoint, $payload);

                if ($response->successful()) {
                    Log::info("RestauranteroProService: {$endpoint} OK");
                    return;
                }

                Log::warning("RestauranteroProService: {$endpoint} falló", [
                    'status' => $response->status(),
                    'body'   => $response->body(),
                ]);
            } catch (\Exception $e) {
                Log::error("RestauranteroProService: {$endpoint} excepción", ['error' => $e->getMessage()]);
            }

            $attempts++;
            if ($attempts < 3) sleep($delays[$attempts]);
        }
    }
}
```

***

## 7. Webhooks

| Evento                      | Cuándo se dispara                              |
| --------------------------- | ---------------------------------------------- |
| `snapshot.completed`        | Cubos regenerados después de un Corte Z        |
| `alert.triggered`           | Un KPI cruzó su umbral de alerta               |
| `insight.generated`         | Controllero IA generó el resumen diario        |
| `cash.variance`             | Faltante/sobrante excede el umbral configurado |
| `inventory.low_stock`       | Ingrediente bajo el mínimo recomendado         |
| `purchase.suggestion_ready` | Tiendita generó nueva lista de compra          |

***

## 10. Changelog

### v1.0.3 — Junio 2026

* **`POST /api/v1/ingest/categories`** — Nuevo endpoint (sección 3.6) para sincronizar categorías de Bakery Pro con RC Express. Soporta jerarquía padre/hijo via `parent_identifier`. RC Express debe devolver `rc_express_id` por categoría creada.
* **Sección 3.5 actualizada** — Response de `ingest/catalog` ahora incluye array `items` con campo `identification` generado por RC Express para cada item nuevo. Requerido para Bakery Pro.
* **Checklist actualizado** — Puntos 3, 4, 5 actualizados para incluir sync de categorías antes de productos.
* **Sección 3.6 (Descuentos) renumerada a 3.7** — Para dar lugar a la nueva sección 3.6 de categorías.

### v1.0.2 — Mayo 2026

* **`POST /api/v1/ingest/catalog`** — Nuevo endpoint para sincronizar el catálogo de platillos del POS.
* **Sección 3.5** — Documentación completa del endpoint de catálogo con mapeo desde RC.
* **Checklist** — Actualizado a 14 puntos.

### v1.0.1 — Mayo 2026

* **`POST /api/v1/ingest/orders`** — Agregado campo `waiter_name` por línea.
* **Checklist** — Actualizado a 13 puntos.

### v1.0.0 — Mayo 2026

**Release inicial de la API pública de Restaurantero Pro.**

***

*Restaurantero Pro — API Documentation v1.0.3*\
*Desarrollado por Restaurant Controller · Hermosillo, Sonora, México*\
*Junio 2026 · Confidencial — Solo para partners autorizados*
