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.
Introducción
Qué es Restaurantero Pro
Restaurantero Pro es una plataforma de inteligencia financiera y operacional para restaurantes y bares. No es un POS — es la capa analítica que se instala encima de cualquier POS.
Tu POS envía los datos transaccionales del día (ventas, pagos, cortes, productos vendidos). Restaurantero Pro los procesa, los convierte en inteligencia y los devuelve como:
- Estado de Resultados (P&L) en tiempo real
- Food Cost, Beverage Cost, Labor Cost vs benchmarks del sector
- Estado de Cuenta de Efectivo (libro de caja digital)
- Reconciliación de caja por turno y por día
- Matriz de Menú (cuáles platillos son Estrella, Puzzle, Caballo de batalla o Perro)
- Alertas automáticas cuando un KPI cruza el umbral de riesgo
- Tiendita Inteligente — lista de compra basada en ventas reales + stock disponible
- Agente IA “Controllero Financiero” que responde preguntas financieras en español mexicano
A quién va dirigida esta API
Esta documentación es para desarrolladores de sistemas POS que quieren conectar su plataforma a Restaurantero Pro. Si eres Carlos (dev de Restaurant Controller), la Sección 6 está escrita específicamente para ti, con el mapeo exacto de los modelos de RC a los endpoints de Restaurantero Pro.
Filosofía de integración
POS Restaurantero Pro
──────────────────── ────────────────────────────────────────
Datos brutos → Inteligencia procesada
Corte Z → P&L del día, food cost, alertas
Líneas de orden → Matriz de menú, product mix
Pagos → Reconciliación, flujo de efectivo
Cancelaciones → Void alerts, análisis de pérdidas
La integración es unidireccional en la ingesta (tu POS envía, nosotros recibimos) y bidireccional en la inteligencia (puedes consumir los datos procesados de vuelta para mostrarlos en tu POS o app).
Environments
| Environment | Base URL | Propósito |
|---|
| Producción | https://api.restauranteropro.com/api/v1 | Único ambiente disponible actualmente |
Por ahora solo existe el ambiente de producción. Para pruebas de integración se utiliza una API Key con prefijo rpro_test_ que apunta al mismo servidor. Los datos de prueba se generan en la misma base de datos — coordina con el equipo de Restaurantero Pro para limpiarlos después de tus pruebas.
Versionado
La API usa prefijo de versión en la URL (/v1/). Cuando haya cambios breaking, se publicará una versión nueva (/v2/) con un período de convivencia mínimo de 90 días antes de deprecar la anterior. Los cambios no-breaking (campos nuevos opcionales, endpoints nuevos) se agregan sin cambio de versión.
Soporte para partners
- Email: dev@restauranteropro.com
- Slack: canal
#partners-api (acceso por invitación al registrarte como partner)
- Status y uptime:
https://status.restauranteropro.com
1. Autenticación
API Keys — mecanismo principal
Cada sucursal (branch) tiene su propia API Key. Esto permite que una misma cadena con 5 restaurantes tenga 5 keys independientes, cada una con sus propios datos aislados.
Formato:
rpro_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ← Producción (datos reales)
rpro_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ← Pruebas (mismo servidor, misma BD)
Cómo se incluye en cada request:
X-API-Key: rpro_live_abc123xyz789...
Content-Type: application/json
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Cómo obtener una API Key:
- El operador del restaurante crea su cuenta en
https://api.restauranteropro.com
- Va a Ajustes → Integraciones → API Keys
- Genera una key para la sucursal que quiere conectar
- Copia la key y la pega en la configuración de su POS
La API Key solo se muestra una vez al generarse. Si se pierde, se genera una nueva y la anterior se revoca automáticamente.
OAuth2 Bearer Token — flujo avanzado
Para integraciones que requieren autorización delegada (el usuario del POS autoriza explícitamente a Restaurantero Pro a leer sus datos), se soporta OAuth2 con Laravel Passport.
Authorization: Bearer {access_token}
El flujo completo de OAuth2 está disponible en el Dev Portal. Para la mayoría de integraciones de POS, la API Key es suficiente y más simple.
Rate limits por plan
| Plan | Requests/minuto | Requests/mes |
|---|
| Starter | 100 | 50,000 |
| Growth | 500 | 500,000 |
| Pro | 2,000 | Ilimitado |
| Enterprise | Sin límite | Sin límite |
Cuando se supera el rate limit, la API responde 429 Too Many Requests con el header Retry-After: {segundos}.
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
Todos los requests y responses usan JSON. Sigue una estructura inspirada en JSON:API:
{
"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
Todos los campos monetarios son enteros que representan centavos. Nunca se envían decimales. El error más común al integrar es enviar un decimal como 534.50 en lugar de 53450.
$150.00 MXN → 15000 (centavos)
$534.50 MXN → 53450 (centavos)
$1,200.00 MXN → 120000 (centavos)
// CORRECTO
"total_sales_cents": 53450
// INCORRECTO — nunca envíes decimales
"total_sales": 534.50
Fechas y horas
- Todas las fechas en formato ISO 8601:
YYYY-MM-DD
- Todos los timestamps en ISO 8601 con timezone:
2026-05-15T23:45:00-07:00
- La timezone se toma de la configuración de la sucursal en Restaurantero Pro. Si la sucursal está en Hermosillo (sin cambio de horario), usar
-07:00 todo el año.
- Los períodos de consulta usan
date_from y date_to como parámetros de query.
Idempotencia — obligatoria en todos los POST de ingesta
Todos los endpoints POST /api/v1/ingest/* requieren el header X-Idempotency-Key con un UUID v4 único por operación.
Por qué es obligatorio: Si la red falla y tu POS reintenta el mismo request, el sistema procesará los datos exactamente una vez, aunque el request llegue 2 o 3 veces. Sin este header, podrías duplicar datos.
Cómo funciona:
1. Tu POS genera un UUID v4 para el Corte Z del día
2. Lo guarda localmente asociado a ese corte
3. Lo incluye en el header X-Idempotency-Key
4. Si el request falla por red, reintenta con el MISMO UUID
5. Restaurantero Pro responde 200 con el resultado previo, sin procesar dos veces
Regla de negocio para RC: Usa el identifier (UUID) del CashRegisterOpening como Idempotency Key. Es único por corte y ya lo tienes disponible.
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Parámetros de query para endpoints de inteligencia
Todos los endpoints GET /api/v1/intelligence/* aceptan estos parámetros:
| Parámetro | Tipo | Requerido | Descripción |
|---|
branch_identifier | UUID | Sí | UUID de la sucursal |
date_from | YYYY-MM-DD | Sí | Inicio del período |
date_to | YYYY-MM-DD | Sí | Fin del período |
period | string | No | Shortcut: today, week, month, yesterday |
Estructura de respuesta exitosa
{
"data": {
"type": "pnl_summary",
"attributes": { },
"meta": {
"period": { "from": "2026-05-01", "to": "2026-05-15" },
"branch_identifier": "uuid",
"snapshot_date": "2026-05-15T06:00:00Z",
"benchmarks": {
"food_cost_status": "green",
"labor_cost_status": "yellow",
"ebitda_status": "green"
}
}
}
}
Estructura de error
{
"error": {
"code": "RPRO_ERR_422",
"message": "El campo total_sales_cents es requerido.",
"field": "data.attributes.total_sales_cents",
"request_id": "req_abc123"
}
}
| Header | Descripción |
|---|
X-Request-Id | ID único del request para soporte |
X-RateLimit-Limit | Límite de requests por minuto de tu plan |
X-RateLimit-Remaining | Requests restantes en la ventana actual |
X-RateLimit-Reset | Timestamp Unix cuando se resetea el contador |
X-Idempotent-Replayed | true si la respuesta es un replay de idempotencia |
Cómo calcular los montos de venta
Todos los campos de ventas en los endpoints de ingesta deben reflejar los totales netos calculados por tu sistema POS — suma de órdenes pagadas menos descuentos, cancelaciones y voids. Nunca deben ser capturados manualmente por el cajero ni estimados.
La regla aplica a todos los endpoints de ingesta:
| Endpoint | Campos afectados |
|---|
POST /ingest/cash-register | cash_sales, card_sales, transfer_sales en summaries[] |
POST /ingest/sales | total_sales_cents, total_sales_no_iva_cents, food_sales_cents, beverage_sales_cents |
POST /ingest/payments | amount_cents por cada método de pago |
POST /ingest/orders | subtotal_cents, net_total_cents por cada línea |
Fórmula correcta para cada monto:
total_sales_cents = Σ(órdenes pagadas) − descuentos − cancelaciones − voids
cash_sales_cents = Σ(pagos en efectivo de órdenes pagadas)
card_sales_cents = Σ(pagos con tarjeta de órdenes pagadas)
net_total_cents = subtotal_cents − discount_cents (por línea de orden)
El único dato que puede venir del cajero:
cash_variance en el Corte Z — es la diferencia entre el efectivo que el sistema esperaba y lo que el cajero contó físicamente al cerrar. Este es el único campo que refleja una captura manual, y es precisamente lo que Restaurantero Pro usa para detectar faltantes y sobrantes de caja.
cash_variance = efectivo_contado_por_cajero − efectivo_esperado_por_sistema
Un valor negativo indica faltante. Un valor positivo indica sobrante.
3. Endpoints de Ingesta
Estos son los endpoints que tu POS implementa. Tu sistema llama a estos endpoints para enviarnos datos. Nosotros los procesamos y generamos la inteligencia.
El orden recomendado al cerrar un Corte Z es:
1. POST /api/v1/ingest/orders # primero las líneas de lo que se vendió
2. POST /api/v1/ingest/discounts # descuentos aplicados durante el turno
3. POST /api/v1/ingest/cancellations # cancelaciones y voids del turno
4. POST /api/v1/ingest/payments # cómo se pagó
5. POST /api/v1/ingest/sales # resumen de ventas del corte
6. POST /api/v1/ingest/cash-register # el corte en sí (X o Z) — este dispara el procesamiento
El Corte Z (cash-register con tipo zout) es el trigger principal. Al recibirlo, Restaurantero Pro regenera todos los cubos del día y activa las alertas correspondientes.
3.1 Corte de caja — POST /api/v1/ingest/cash-register
Registra un corte de caja. En Restaurant Controller existen dos tipos:
- Corte X (
xout): cierre de turno parcial — informativo, no cierra el día
- Corte Z (
zout): cierre del día — trigger principal que procesa todos los cubos
Este endpoint mapea directamente al modelo CashRegisterOpening de RC.
Headers requeridos:
X-API-Key: rpro_live_xxx
X-Idempotency-Key: {CashRegisterOpening.identifier}
Content-Type: application/json
Body del request:
{
"data": {
"type": "cash-register",
"attributes": {
"branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"closing_type": "zout",
"start_at": "2026-05-15T07:00:00-07:00",
"end_at": "2026-05-15T23:45:00-07:00",
"status": "completed",
"summaries": [
{ "type": "cash_sales", "name": "Venta Efectivo", "amount_cents": 125000 },
{ "type": "card_sales", "name": "Venta Tarjeta", "amount_cents": 409550 },
{ "type": "transfer_sales", "name": "Transferencia", "amount_cents": 0 },
{ "type": "tips_received", "name": "Propinas Cobradas", "amount_cents": 30000 },
{ "type": "tips_paid_to_waiter", "name": "Propinas Pagadas a Mesero", "amount_cents": 22000 },
{ "type": "withholding", "name": "Retención (Moche)", "amount_cents": 5000 },
{ "type": "deposits", "name": "Depósitos a Caja", "amount_cents": 10000 },
{ "type": "cash_variance", "name": "Sobrante / Faltante", "amount_cents": 314 }
]
},
"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",
"cash_register_name": "CAJA 1"
}
}
}
Tipos de summary disponibles:
| type | Descripción |
|---|
cash_sales | Total vendido en efectivo |
card_sales | Total vendido con tarjeta (crédito + débito) |
card_credit_sales | Tarjeta crédito (opcional, desglose) |
card_debit_sales | Tarjeta débito (opcional, desglose) |
transfer_sales | Transferencia bancaria |
delivery_sales | Apps de delivery (Rappi, Uber Eats, DiDi Food) |
voucher_sales | Vales de despensa |
tips_received | Propinas cobradas a clientes |
tips_paid_to_waiter | Propinas entregadas a meseros |
withholding | Retención (moche al mesero) |
deposits | Depósitos al fondo de caja |
cash_variance | Diferencia entre efectivo esperado y real (positivo = sobrante, negativo = faltante) |
Response exitoso 202 Accepted:
{
"data": {
"type": "cash-register-ingest",
"id": "ingest_uuid",
"attributes": {
"queued": true,
"closing_type": "zout",
"message": "Corte Z recibido. Los cubos del día se regenerarán en los próximos segundos."
}
}
}
La respuesta es 202 Accepted, no 200 OK. El procesamiento ocurre de forma asíncrona en cola. Usa webhooks (snapshot.completed) para saber cuándo los datos están listos para consultar.
3.2 Ventas — POST /api/v1/ingest/sales
Resumen de ventas del corte o del día. Complementa al cash-register con el desglose por categoría de venta.
Body del request:
{
"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,
"avg_pax_cents": 6144
},
"meta": {
"pos_name": "restaurant_controller",
"pos_version": "3.2.1",
"sent_at": "2026-05-15T23:46:00Z"
}
}
}
Valores válidos para shift:
| Valor | Descripción |
|---|
breakfast | Desayuno |
lunch | Comida |
dinner | Cena |
bar | Bar / late night |
all_day | Cuando no se separa por turno |
Response exitoso 202 Accepted:
{
"data": {
"type": "sales-ingest",
"attributes": { "queued": true }
}
}
3.3 Pagos por método — POST /api/v1/ingest/payments
Desglose de cómo se pagaron las ventas del día. Mapea los Payment de RC agrupados por PaymentType.
Body del request:
{
"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
},
{
"payment_type": "card_debit",
"payment_type_name": "Tarjeta Débito",
"amount_cents": 129550,
"tips_cents": 7000,
"change_cents": 0,
"transaction_count": 23
}
]
},
"meta": {
"pos_name": "restaurant_controller",
"sent_at": "2026-05-15T23:46:00Z"
}
}
}
Tipos de pago estándar:
| payment_type | Descripción |
|---|
cash | Efectivo |
card_credit | Tarjeta crédito |
card_debit | Tarjeta débito |
card | Tarjeta (sin distinción crédito/débito) |
transfer | Transferencia bancaria / SPEI |
rappi | Rappi |
uber_eats | Uber Eats |
didi_food | DiDi Food |
voucher | Vales de despensa |
other | Otro |
3.4 Líneas de orden — POST /api/v1/ingest/orders
Envía los productos vendidos durante el período. Cada línea genera una fila independiente en la base de datos — esto permite queries directas para Top 10 productos, Star Employees (ranking de meseros) y Ventas por categoría en el dashboard. Mapea Order → OrderTicket → OrderTicketLine de RC.
Body del request:
{
"data": {
"type": "orders",
"attributes": {
"branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"date": "2026-05-15",
"closing_type": "zout",
"lines": [
{
"item_identifier": "item-uuid-1",
"item_sku": "ARRACHERA-01",
"item_name": "Arrachera a las Brasas",
"category": "Cortes",
"category_type": "food",
"waiter_name": "Ana García",
"quantity": 14,
"unit_price_cents": 34500,
"subtotal_cents": 483000,
"discount_cents": 0,
"net_total_cents": 483000
},
{
"item_identifier": "item-uuid-2",
"item_sku": "MARGARITA-01",
"item_name": "Margarita Clásica",
"category": "Cocteles",
"category_type": "beverage",
"waiter_name": "Luis Herrera",
"quantity": 32,
"unit_price_cents": 12000,
"subtotal_cents": 384000,
"discount_cents": 12000,
"net_total_cents": 372000
}
]
},
"meta": {
"pos_name": "restaurant_controller",
"sent_at": "2026-05-15T23:45:00Z"
}
}
}
Campos por línea:
| Campo | Tipo | Requerido | Descripción |
|---|
item_name | string | ✅ Sí | Nombre del platillo o producto |
quantity | integer | ✅ Sí | Cantidad vendida (entero positivo) |
unit_price_cents | integer | ✅ Sí | Precio unitario en centavos |
subtotal_cents | integer | ✅ Sí | Subtotal antes de descuento en centavos |
net_total_cents | integer | ✅ Sí | Total neto después de descuento en centavos |
category_type | string | ✅ Sí | Clasificación del producto (ver tabla abajo) |
waiter_name | string | ⭐ Recomendado | Nombre del mesero — alimenta Star Employees en el dashboard |
item_identifier | uuid | No | UUID del item en RC — permite cruce con recetario |
item_sku | string | No | SKU del producto en RC |
category | string | No | Nombre de la categoría (ej. “Cortes”, “Cocteles”) |
discount_cents | integer | No | Descuento aplicado en centavos (default: 0) |
Valores válidos para category_type:
| Valor | Descripción | Alimenta en el dashboard |
|---|
food | Alimentos | Food Cost %, Top 10, Ventas por categoría |
beverage | Bebidas sin alcohol | Beverage Cost %, Top 10 |
bar | Bebidas con alcohol | Bar Cost %, Top 10 |
other | Sin clasificación | Top 10 |
waiter_name es clave para el ranking de meseros (Star Employees) en el dashboard del dueño. Sin este campo, el módulo de Star Employees no tendrá datos. Envíalo en cada línea con el nombre completo del mesero que tomó la orden.
Usa el identifier del Item del POS como item_identifier. Esto permite que Restaurantero Pro cruce con el recetario si lo registraste.
3.5 Descuentos — POST /api/v1/ingest/discounts
Registra los descuentos y cortesías aplicados durante el período. Mapea el modelo Discount de RC.
Body del request:
{
"data": {
"type": "discounts",
"attributes": {
"branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"date": "2026-05-15",
"closing_type": "zout",
"discounts": [
{
"discount_identifier": "disc-uuid-1",
"type": "percentage",
"factor_type": "percentage",
"name": "Descuento Empleado",
"rate": 50,
"amount_cents": 18500,
"applied_to": "order",
"applied_by": "María González",
"authorized_by": "Roberto Pérez",
"reason": "Descuento a empleado"
},
{
"discount_identifier": "disc-uuid-2",
"type": "courtesy",
"factor_type": "courtesy",
"name": "Cortesía Gerencia",
"rate": 100,
"amount_cents": 43000,
"applied_to": "order",
"applied_by": "Roberto Pérez",
"authorized_by": "Roberto Pérez",
"reason": "Invitación a cliente VIP"
}
]
},
"meta": {
"pos_name": "restaurant_controller",
"sent_at": "2026-05-15T23:45:00Z"
}
}
}
Tipos de descuento:
| type / factor_type | Descripción |
|---|
percentage | Porcentaje sobre el subtotal |
fixed | Monto fijo en centavos |
courtesy | Cortesía — 100% del monto |
3.6 Cancelaciones — POST /api/v1/ingest/cancellations
Registra cancelaciones de órdenes y líneas (voids). Mapea el modelo Cancellation de RC. Las cancelaciones excesivas disparan alertas automáticas en el dashboard del operador.
Body del request:
{
"data": {
"type": "cancellations",
"attributes": {
"branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"date": "2026-05-15",
"closing_type": "zout",
"cancellations": [
{
"cancellation_identifier": "canc-uuid-1",
"item_name": "Filete al Gusto",
"item_sku": "FILETE-02",
"quantity": 1,
"amount_cents": 38500,
"reason": "Error de captura",
"cancelled_by": "Luis Herrera",
"authorized_by": "Roberto Pérez",
"cancelled_at": "2026-05-15T21:30:00-07:00"
},
{
"cancellation_identifier": "canc-uuid-2",
"item_name": "Corona Extra",
"item_sku": "CORONA-355",
"quantity": 2,
"amount_cents": 9000,
"reason": "Cliente cambió de opinión",
"cancelled_by": "Ana García",
"authorized_by": "Roberto Pérez",
"cancelled_at": "2026-05-15T20:15:00-07:00"
}
]
},
"meta": {
"pos_name": "restaurant_controller",
"sent_at": "2026-05-15T23:45:00Z"
}
}
}
4. Endpoints de Inteligencia
Estos son los endpoints que consumes para mostrar datos procesados. Los puedes integrar en el dashboard del POS, en la app del dueño, o en cualquier superficie.
Todos requieren X-API-Key y los parámetros branch_identifier, date_from, date_to.
4.1 Resumen de ventas
GET /api/v1/intelligence/sales/summary
Parámetros:
branch_identifier=uuid
date_from=2026-05-01
date_to=2026-05-15
Response:
{
"data": {
"type": "sales_summary",
"attributes": {
"total_sales_cents": 8234500,
"total_sales_no_iva_cents": 7098707,
"food_sales_cents": 5648741,
"beverage_sales_cents": 2585759,
"bar_sales_cents": 0,
"covers_total": 1341,
"tables_served_total": 372,
"avg_ticket_cents": 6143,
"avg_pax": 3.6,
"vs_previous_period": {
"total_sales_pct_change": 12.4,
"covers_pct_change": 8.1,
"avg_ticket_pct_change": 3.9
},
"by_shift": {
"breakfast": { "sales_cents": 0, "covers": 0 },
"lunch": { "sales_cents": 5234500, "covers": 854 },
"dinner": { "sales_cents": 3000000, "covers": 487 },
"bar": { "sales_cents": 0, "covers": 0 }
}
},
"meta": {
"period": { "from": "2026-05-01", "to": "2026-05-15" },
"branch_identifier": "uuid"
}
}
}
4.2 Ventas por hora
GET /api/v1/intelligence/sales/by-hour
Útil para mapas de calor de ocupación y optimización de staffing.
Response:
{
"data": {
"type": "sales_by_hour",
"attributes": {
"peak_hour": "14:00",
"valley_hour": "10:00",
"hours": [
{ "hour": "13:00", "sales_cents": 824500, "covers": 134, "tables": 38 },
{ "hour": "14:00", "sales_cents": 1234000, "covers": 201, "tables": 56 },
{ "hour": "15:00", "sales_cents": 982000, "covers": 159, "tables": 44 }
]
}
}
}
4.3 Ventas por empleado
GET /api/v1/intelligence/sales/by-employee
Ranking de desempeño de meseros. Star Employees.
Este endpoint requiere que hayas enviado waiter_name en las líneas de orden vía POST /api/v1/ingest/orders.
Response:
{
"data": {
"type": "sales_by_employee",
"attributes": {
"employees": [
{
"name": "Ana García",
"total_sales_cents": 1823400,
"covers": 298,
"avg_ticket_cents": 6117,
"tips_cents": 182340,
"tip_pct": 10.0,
"rank": 1
},
{
"name": "Luis Herrera",
"total_sales_cents": 1456800,
"covers": 241,
"avg_ticket_cents": 6045,
"tips_cents": 130000,
"tip_pct": 8.9,
"rank": 2
}
]
}
}
}
4.4 Reconciliación de caja
GET /api/v1/intelligence/sales/reconciliation
Tabla detallada de cada corte con efectivo, tarjeta, propinas, y faltante/sobrante. Exportable a PDF.
Response:
{
"data": {
"type": "reconciliation",
"attributes": {
"totals": {
"cash_sales_cents": 1850000,
"card_sales_cents": 6384500,
"tips_cents": 423000,
"grand_total_cents": 8234500,
"variance_cents": 3140
},
"records": [
{
"date": "2026-05-15",
"shift": "dinner",
"folio": "Z-00045",
"cashier": "Ana García",
"cash_cents": 125000,
"card_cents": 409550,
"tips_cents": 30000,
"total_cents": 534550,
"variance_cents": 314,
"variance_status": "green"
}
]
}
}
}
4.5 Estado de Cuenta de Efectivo (Cash Flow)
GET /api/v1/intelligence/cash-flow
El libro de caja digital del restaurante. Muestra todas las entradas y salidas de efectivo del período, con saldo inicial y saldo final. Equivale al control de efectivo que antes se hacía en Excel.
Response:
{
"data": {
"type": "cash_flow",
"attributes": {
"period_summary": {
"opening_balance_cents": 500000,
"total_inflows_cents": 8734500,
"total_outflows_cents": 7284200,
"closing_balance_cents": 1950300,
"net_cash_cents": 1450300
},
"inflows": {
"cash_sales_cents": 1850000,
"deposits_received_cents": 100000,
"other_income_cents": 0,
"tips_received_cents": 423000
},
"outflows": {
"cash_purchases_cents": 485000,
"cash_expenses_cents": 342000,
"tips_paid_to_waiters_cents": 380000,
"withholding_cents": 75000,
"other_cash_out_cents": 0
},
"daily_entries": [
{
"date": "2026-05-15",
"opening_balance_cents": 500000,
"inflows_cents": 534550,
"outflows_cents": 67000,
"closing_balance_cents": 967550,
"variance_cents": 314,
"variance_pct": 0.06,
"variance_status": "green"
}
],
"alerts": [
{
"date": "2026-05-08",
"type": "variance_high",
"message": "Faltante de caja de $850 MXN detectado",
"severity": "yellow"
}
]
},
"meta": {
"period": { "from": "2026-05-01", "to": "2026-05-15" },
"benchmark": {
"variance_pct_threshold": 1.0,
"variance_status": "green"
}
}
}
}
4.6 Estado de Resultados (P&L)
GET /api/v1/intelligence/pnl
El corazón del producto. Estado de Resultados completo del período, calculado cruzando ventas + compras + nómina capturadas en el dashboard.
Response:
{
"data": {
"type": "pnl_summary",
"attributes": {
"ventas": {
"food_sales_cents": 5648741,
"beverage_sales_cents": 2585759,
"bar_sales_cents": 0,
"total_net_sales_cents": 8234500,
"food_sales_pct": 68.6,
"beverage_sales_pct": 31.4
},
"costo_de_ventas": {
"food_cost_cents": 1694622,
"food_cost_pct": 30.0,
"food_cost_status": "yellow",
"beverage_cost_cents": 517152,
"beverage_cost_pct": 20.0,
"beverage_cost_status": "green",
"total_cogs_cents": 2211774
},
"utilidad_bruta": {
"gross_profit_cents": 6022726,
"gross_margin_pct": 73.1
},
"gastos_operativos": {
"labor_cost_cents": 2470350,
"labor_cost_pct": 30.0,
"labor_cost_status": "yellow",
"prime_cost_cents": 4682124,
"prime_cost_pct": 56.9,
"prime_cost_status": "green",
"rent_cents": 900000,
"utilities_cents": 370000,
"maintenance_cents": 164000,
"marketing_cents": 240000,
"admin_cents": 136000,
"other_expenses_cents": 136000,
"total_opex_cents": 4416350
},
"ebitda": {
"ebitda_cents": 1606376,
"ebitda_pct": 19.5,
"ebitda_status": "yellow"
},
"utilidad_neta": {
"net_profit_cents": 1446376,
"net_profit_pct": 17.6,
"net_profit_status": "green"
}
},
"meta": {
"period": { "from": "2026-05-01", "to": "2026-05-15" },
"benchmarks": {
"food_cost": { "green": "< 30%", "yellow": "30-38%", "red": "> 38%" },
"labor_cost": { "green": "< 30%", "yellow": "30-35%", "red": "> 35%" },
"prime_cost": { "green": "< 60%", "yellow": "60-65%", "red": "> 65%" },
"ebitda": { "green": "> 20%", "yellow": "10-20%", "red": "< 10%" }
}
}
}
}
4.7 Food Cost & Beverage Cost
GET /api/v1/intelligence/food-cost
Desglose de costos de materia prima por categoría y platillo. Requiere que el operador haya capturado compras en el dashboard.
Response:
{
"data": {
"type": "food_cost",
"attributes": {
"food_cost_pct": 30.0,
"food_cost_status": "yellow",
"beverage_cost_pct": 20.0,
"beverage_cost_status": "green",
"bar_cost_pct": 0.0,
"by_category": [
{
"category": "Carnes",
"purchases_cents": 845000,
"sales_cents": 2350000,
"cost_pct": 35.9,
"status": "yellow"
},
{
"category": "Mariscos",
"purchases_cents": 420000,
"sales_cents": 1850000,
"cost_pct": 22.7,
"status": "green"
}
]
}
}
}
4.8 Labor Cost
GET /api/v1/intelligence/labor-cost
Costo de nómina como porcentaje de ventas, por departamento. Requiere que el operador haya capturado nómina en el dashboard.
Response:
{
"data": {
"type": "labor_cost",
"attributes": {
"total_labor_cents": 2470350,
"labor_cost_pct": 30.0,
"labor_cost_status": "yellow",
"by_department": [
{ "department": "Cocina", "amount_cents": 1050000, "pct_of_sales": 12.8 },
{ "department": "Servicio", "amount_cents": 870000, "pct_of_sales": 10.6 },
{ "department": "Administración", "amount_cents": 350000, "pct_of_sales": 4.3 },
{ "department": "Bar", "amount_cents": 200350, "pct_of_sales": 2.4 }
]
}
}
}
4.9 KPIs consolidados
GET /api/v1/intelligence/kpis
Todos los KPIs en un solo endpoint con semáforo. Ideal para el widget de resumen del dashboard del POS.
Response:
{
"data": {
"type": "kpis",
"attributes": {
"kpis": [
{ "key": "food_cost_pct", "label": "Food Cost", "value": 30.0, "unit": "%", "status": "yellow", "benchmark_green": "< 30%", "benchmark_red": "> 38%" },
{ "key": "beverage_cost_pct", "label": "Beverage Cost", "value": 20.0, "unit": "%", "status": "green", "benchmark_green": "< 20%", "benchmark_red": "> 28%" },
{ "key": "labor_cost_pct", "label": "Labor Cost", "value": 30.0, "unit": "%", "status": "yellow", "benchmark_green": "< 30%", "benchmark_red": "> 35%" },
{ "key": "prime_cost_pct", "label": "Prime Cost", "value": 56.9, "unit": "%", "status": "green", "benchmark_green": "< 60%", "benchmark_red": "> 65%" },
{ "key": "ebitda_pct", "label": "EBITDA", "value": 19.5, "unit": "%", "status": "yellow", "benchmark_green": "> 20%", "benchmark_red": "< 10%" },
{ "key": "net_profit_pct", "label": "Utilidad Neta", "value": 17.6, "unit": "%", "status": "green", "benchmark_green": "> 15%", "benchmark_red": "< 5%" },
{ "key": "avg_ticket_cents", "label": "Ticket Prom.", "value": 6143, "unit": "cents", "status": "green", "benchmark_green": "> 28000", "benchmark_red": "< 18000" },
{ "key": "cash_variance_pct", "label": "Varianza Caja", "value": 0.06, "unit": "%", "status": "green", "benchmark_green": "< 1%", "benchmark_red": "> 3%" }
]
},
"meta": {
"period": { "from": "2026-05-01", "to": "2026-05-15" }
}
}
}
Semáforo de benchmarks (industria restaurantera México):
| KPI | Verde | Amarillo | Rojo |
|---|
| Food Cost % | < 30% | 30–38% | > 38% |
| Beverage Cost % | < 20% | 20–28% | > 28% |
| Bar Cost % | < 25% | 25–30% | > 30% |
| Labor Cost % | < 30% | 30–35% | > 35% |
| Prime Cost % | < 60% | 60–65% | > 65% |
| EBITDA % | > 20% | 10–20% | < 10% |
| Utilidad Neta % | > 15% | 5–15% | < 5% |
| Varianza Caja | < 1% | 1–3% | > 3% |
| Inventario DSI | < 7 días | 7–14 días | > 14 días |
4.10 Alertas activas
GET /api/v1/intelligence/alerts
Lista priorizada de alertas del período.
Response:
{
"data": {
"type": "alerts",
"attributes": {
"total_count": 3,
"critical_count": 0,
"warning_count": 2,
"info_count": 1,
"alerts": [
{
"id": "alert-uuid-1",
"severity": "yellow",
"type": "food_cost_high",
"title": "Food Cost en zona de atención",
"message": "Tu Food Cost es 30.0% — está en el límite del rango recomendado (< 30%). Revisa los costos de materia prima de Carnes.",
"action": "Revisar compras de Carnes en el período",
"created_at": "2026-05-15T06:00:00Z"
}
]
}
}
}
Tipos de alerta:
| type | Severidad típica | Descripción |
|---|
food_cost_high | yellow / red | Food cost sobre benchmark |
bar_cost_high | yellow / red | Bar cost sobre benchmark |
labor_cost_high | yellow / red | Labor cost sobre benchmark |
cash_variance_high | yellow / red | Faltante/sobrante de caja excesivo |
discounts_high | yellow / red | Descuentos inusuales o excesivos |
cancellations_high | yellow / red | Cancelaciones sobre el umbral normal |
void_alert | yellow / red | Voids sospechosos por usuario |
low_stock | yellow | Inventario bajo mínimo recomendado |
sales_up | blue | Ventas superiores al período anterior |
sales_down | yellow / red | Ventas inferiores al período anterior |
4.11 Matriz de Menú
GET /api/v1/intelligence/menu/engineering
Clasifica cada platillo en uno de 4 cuadrantes según rentabilidad y popularidad.
Requiere que hayas enviado las líneas de orden vía POST /api/v1/ingest/orders con category_type y item_name correctos.
Response:
{
"data": {
"type": "menu_engineering",
"attributes": {
"summary": {
"stars_count": 8,
"puzzles_count": 4,
"plowhorses_count": 6,
"dogs_count": 3
},
"items": [
{
"item_sku": "ARRACHERA-01",
"item_name": "Arrachera a las Brasas",
"category": "Cortes",
"quantity_sold": 94,
"popularity_pct": 8.2,
"unit_price_cents": 34500,
"unit_cost_cents": 10350,
"contribution_cents": 24150,
"total_contribution_cents": 2270100,
"quadrant": "star",
"quadrant_label": "Estrella",
"action": "Proteger — no cambiar precio ni receta"
}
]
}
}
}
Cuadrantes de la Matriz de Menú:
| Cuadrante | Rentabilidad | Popularidad | Acción recomendada |
|---|
| Estrella (Star) | Alta | Alta | Proteger — no tocar precio ni receta |
| Puzzle | Alta | Baja | Promover — potencial desaprovechado |
| Caballo (Plowhorse) | Baja | Alta | Reducir costo o mantener por volumen |
| Perro (Dog) | Baja | Baja | Eliminar o rediseñar |
4.12 Tiendita Inteligente — Sugerencias de compra
GET /api/v1/intelligence/inventory/suggestions
Lista de compra recomendada para los próximos días, basada en ventas reales + stock disponible + lead time del proveedor. Requiere que el operador tenga inventario capturado en el dashboard.
Response:
{
"data": {
"type": "purchase_suggestions",
"attributes": {
"generated_at": "2026-05-15T06:00:00Z",
"days_ahead": 3,
"by_supplier": [
{
"supplier_name": "Carnes Premium Hermosillo",
"items": [
{
"item_name": "Arrachera USDA Choice",
"unit": "kg",
"current_stock": 4.5,
"suggested_qty": 18.0,
"unit_cost_cents": 22000,
"total_cost_cents": 396000,
"reason": "Stock para 0.8 días — ventas proyectadas de 14 porciones/día"
}
],
"subtotal_cents": 396000
}
],
"total_suggested_cents": 645000
}
}
}
5. Módulo Live — Datos en tiempo real
Estos endpoints devuelven el estado actual del turno, no datos históricos. Úsalos para mostrar información en tiempo real en el panel del gerente durante el servicio.
5.1 Ventas del turno en curso
GET /api/v1/live/sales/today
Response:
{
"data": {
"type": "live_sales",
"attributes": {
"as_of": "2026-05-15T21:30:00-07:00",
"total_sales_cents": 312500,
"covers_so_far": 54,
"tables_so_far": 15,
"avg_ticket_cents": 5787,
"vs_same_time_yesterday": {
"sales_pct_change": 8.3,
"covers_pct_change": 5.1
},
"projected_day_total_cents": 534550
}
}
}
5.2 Saldo de caja en tiempo real
GET /api/v1/live/cash/balance
Efectivo esperado en caja en este momento. Útil para alertas de faltante durante el turno, antes del Corte Z.
Response:
{
"data": {
"type": "live_cash",
"attributes": {
"as_of": "2026-05-15T21:30:00-07:00",
"opening_balance_cents": 500000,
"cash_sales_so_far_cents": 78500,
"cash_out_so_far_cents": 12000,
"expected_balance_cents": 566500,
"status": "green"
}
}
}
5.3 Alertas del turno activo
GET /api/v1/live/alerts/active
Alertas generadas durante el turno actual. El POS puede usarlas para notificaciones en tiempo real al gerente.
Response:
{
"data": {
"type": "live_alerts",
"attributes": {
"alerts": [
{
"severity": "yellow",
"type": "discounts_high",
"message": "Se han aplicado 4 cortesías hoy — 2 más que el promedio diario",
"triggered_at": "2026-05-15T20:45:00-07:00"
}
]
}
}
}
6. Guía de integración — Restaurant Controller POS
Esta sección es para Carlos (dev de RC). Aquí está el mapa exacto de qué conectar, cuándo y cómo — sin ambigüedades.
6.1 Flujo completo de integración
RESTAURANT CONTROLLER POS RESTAURANTERO PRO
───────────────────────────────────────── ──────────────────────────────────
SETUP (una sola vez por sucursal)
────────────────────────────────
1. Marco crea la cuenta en RP → Group + Brand + Branch creados
2. Marco genera la API Key → rpro_live_xxx
3. Carlos pega la key en Settings del RC → RC queda configurado
DURANTE EL SERVICIO (en tiempo real)
─────────────────────────────────────
4. Gerente consulta ventas del turno → GET /api/v1/live/sales/today
5. Gerente ve alertas del turno → GET /api/v1/live/alerts/active
AL CERRAR EL CORTE Z (cada noche)
──────────────────────────────────
6. POST /api/v1/ingest/orders ← líneas de productos vendidos
7. POST /api/v1/ingest/discounts ← descuentos del día
8. POST /api/v1/ingest/cancellations ← cancelaciones y voids
9. POST /api/v1/ingest/payments ← desglose por método de pago
10. POST /api/v1/ingest/sales ← resumen de ventas
11. POST /api/v1/ingest/cash-register ← CORTE Z (dispara procesamiento)
→ RP regenera cubos (asíncrono, ~5 seg)
→ Webhook: snapshot.completed
→ Alertas calculadas automáticamente
→ Insight IA del día generado
AL DÍA SIGUIENTE (dashboard del dueño)
───────────────────────────────────────
12. GET /api/v1/intelligence/pnl ← P&L del día anterior
13. GET /api/v1/intelligence/kpis ← Semáforo de KPIs
14. GET /api/v1/intelligence/alerts ← Alertas priorizadas
6.2 Versión Full Dining (restaurant_pos)
| Modelo RC | Qué contiene | → Endpoint RP |
|---|
CashRegisterOpening (tipo zout) | El corte Z completo con summaries[] | POST /api/v1/ingest/cash-register |
CashRegisterOpeningSummary[] | Efectivo, tarjeta, propinas, retenciones, faltante | Incluido en el body del cash-register |
Order → OrderTicket → OrderTicketLine[] | Productos vendidos, cantidades, precios | POST /api/v1/ingest/orders |
Payment[] (agrupados por PaymentType) | Cómo pagaron los clientes | POST /api/v1/ingest/payments |
Discount[] | Descuentos y cortesías aplicados | POST /api/v1/ingest/discounts |
Cancellation[] | Líneas canceladas y voids | POST /api/v1/ingest/cancellations |
Dónde en RC disparar el envío:
En el CashRegisterOpeningController.php, ya existe la lógica que detecta cuando el closing type es zout:
$isStoredZOut = $closingCategoryType === 'cash_register.closings.zout';
Justo después de que esta condición se cumple y RC termina su proceso interno, es el momento de disparar los POSTs a Restaurantero Pro. El orden correcto es el que se muestra en la sección 6.1.
6.3 Versión Express (express_pos)
Express funciona igual que Full Dining en cuanto a la integración. La diferencia está en que Express no tiene mesas ni zonas de reservación, pero los modelos de datos son idénticos: CashRegisterOpening, OrderTicketLine, Payment, Discount, Cancellation existen en ambas versiones con la misma estructura.
El dispatch del Corte Z en Express también vive en CashRegisterOpeningController.php — misma lógica, mismo punto de integración.
6.4 Mapeo de modelos RC → endpoints
CashRegisterOpening → /api/v1/ingest/cash-register
$closing = $cashRegisterOpening;
$body = [
'data' => [
'type' => 'cash-register',
'attributes' => [
'branch_identifier' => config('restaurantero.branch_identifier'),
'closing_type' => 'zout',
'start_at' => $closing->start_at->toIso8601String(),
'end_at' => $closing->end_at->toIso8601String(),
'status' => 'completed',
'summaries' => $closing->summaries->map(fn($s) => [
'type' => $this->mapSummaryType($s->type),
'name' => $s->name,
'amount_cents' => (int) ($s->amount * 100),
])->toArray(),
],
'meta' => [
'pos_name' => 'restaurant_controller',
'pos_version' => config('app.version'),
'pos_variant' => 'full_dining',
'sent_at' => now()->toIso8601String(),
'cashier_name' => $closing->user?->name,
],
],
];
Mapeo de tipos de summary RC → RP:
| Tipo en RC | Tipo en Restaurantero Pro |
|---|
| Ventas Efectivo / cash | cash_sales |
| Ventas Tarjeta / card | card_sales |
| Ventas Tarjeta Crédito | card_credit_sales |
| Ventas Tarjeta Débito | card_debit_sales |
| Transferencia | transfer_sales |
| Propinas Cobradas | tips_received |
Propinas Pagadas Mesero (tipPaidToWaiter) | tips_paid_to_waiter |
Retención / Moche (withholdingAmount) | withholding |
Depósitos (deposits) | deposits |
| Sobrante / Faltante | cash_variance |
OrderTicketLine → /api/v1/ingest/orders
$lines = OrderTicketLine::query()
->whereHas('ticket.order', fn($q) => $q->whereIn('status', ['closed', 'to_pay']))
->whereDate('created_at', today())
->with(['ticket.order.user', 'ticket.order.waiter', 'item.category'])
->get()
->map(fn($line) => [
'item_identifier' => $line->item?->identifier,
'item_sku' => $line->identification ?? $line->item?->sku,
'item_name' => $line->description,
'category' => $line->item?->category?->name,
'category_type' => $this->mapCategoryType($line->item?->category),
// ⭐ waiter_name — REQUERIDO para Star Employees en el dashboard
'waiter_name' => $line->ticket?->order?->waiter?->name
?? $line->ticket?->order?->user?->name
?? null,
'quantity' => (int) ($line->quantity * 100),
'unit_price_cents' => $line->unit_price,
'subtotal_cents' => (int) ($line->subtotal * 100),
'discount_cents' => (int) ($line->discount * 100),
'net_total_cents' => (int) ($line->netSubtotal * 100),
])->toArray();
Los atributos unit_price y quantity en OrderTicketLine de RC usan mutadores que dividen el valor almacenado entre 100 antes de devolverlo. Si en BD está 1500000, el mutador devuelve 150.00. Para enviar a Restaurantero Pro en centavos, multiplica por 100.
waiter_name es clave para Star Employees en el dashboard. El campo waiter en la orden es el mesero asignado. Si no existe waiter, usar user como fallback.
Payment → /api/v1/ingest/payments
$payments = Payment::query()
->whereDate('created_at', today())
->whereIn('status', ['active', 'paid'])
->with('type')
->get()
->groupBy('type.name')
->map(fn($group, $typeName) => [
'payment_type' => $this->mapPaymentType($typeName),
'payment_type_name' => $typeName,
'amount_cents' => (int) ($group->sum('amount') * 100),
'tips_cents' => 0,
'change_cents' => (int) ($group->sum('change') * 100),
'transaction_count' => $group->count(),
])->values()->toArray();
Discount → /api/v1/ingest/discounts
$discounts = Discount::query()
->whereDate('created_at', today())
->whereIn('status', ['active'])
->with(['user', 'category'])
->get()
->map(fn($d) => [
'discount_identifier' => $d->identifier,
'type' => $d->type,
'factor_type' => $d->factor_type,
'name' => $d->name,
'rate' => $d->rate_or_fee,
'amount_cents' => (int) ($d->rate_or_fee * 100),
'applied_to' => class_basename($d->discountable_type),
'applied_by' => $d->user?->name,
'authorized_by' => $d->user?->name,
'reason' => $d->description,
])->toArray();
Cancellation → /api/v1/ingest/cancellations
$cancellations = Cancellation::query()
->whereDate('created_at', today())
->with(['cancelledBy', 'authorizedBy', 'cancellationable'])
->get()
->map(fn($c) => [
'cancellation_identifier' => $c->identifier,
'item_name' => $c->cancellationable?->description ?? 'Sin nombre',
'item_sku' => $c->cancellationable?->identification,
'quantity' => 1,
'amount_cents' => (int) (($c->cancellationable?->subtotal ?? 0) * 100),
'reason' => $c->reason,
'cancelled_by' => $c->cancelledBy?->name,
'authorized_by' => $c->authorizedBy?->name,
'cancelled_at' => $c->created_at->toIso8601String(),
])->toArray();
6.5 Cuándo disparar cada POST
| Evento en RC | Qué enviar | Notas |
|---|
CashRegisterOpening status → completed AND tipo = zout | Todos los 6 POSTs en orden | El trigger principal del día |
CashRegisterOpening status → completed AND tipo = xout | Solo POST /ingest/cash-register con closing_type: "xout" | Informativo, no dispara cubos |
| En tiempo real, cada 5-10 min (opcional) | GET /api/v1/live/sales/today | Para mostrar ventas del día en panel del gerente |
| Al cargar el dashboard del dueño | GET /api/v1/intelligence/kpis + GET /api/v1/intelligence/alerts | Muestra el resumen del día anterior |
6.6 Manejo de idempotencia
Paso 1: El CashRegisterOpening ya tiene un identifier UUID — úsalo directamente:
$idempotencyKey = $cashRegisterOpening->identifier;
// Ejemplo: "550e8400-e29b-41d4-a716-446655440000"
Paso 2: Incluye el mismo UUID en todos los 6 POSTs del mismo Corte Z con sufijo:
'X-Idempotency-Key' => $cashRegisterOpening->identifier, // cash-register
'X-Idempotency-Key' => $cashRegisterOpening->identifier . '-orders',
'X-Idempotency-Key' => $cashRegisterOpening->identifier . '-payments',
'X-Idempotency-Key' => $cashRegisterOpening->identifier . '-discounts',
'X-Idempotency-Key' => $cashRegisterOpening->identifier . '-cancellations',
'X-Idempotency-Key' => $cashRegisterOpening->identifier . '-sales',
Paso 3: Si el request falla (timeout, error de red), reintenta con el mismo Idempotency Key. Restaurantero Pro detecta el duplicado y responde 200 sin procesar dos veces.
Política de reintentos recomendada:
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 al día siguiente
6.7 Snippet PHP listo para copiar
<?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://api.restauranteropro.com/api/v1');
$this->apiKey = config('restaurantero.api_key');
$this->branchIdentifier = config('restaurantero.branch_identifier');
}
/**
* Envía el Corte Z completo a Restaurantero Pro.
* Llama a este método justo después de que RC procesa el cierre del día.
*/
public function sendZOut(CashRegisterOpening $closing): void
{
$closingId = $closing->identifier;
// 1. Líneas de orden (primero)
$this->post('/ingest/orders', $this->buildOrdersPayload($closing), $closingId . '-orders');
// 2. Descuentos
$this->post('/ingest/discounts', $this->buildDiscountsPayload($closing), $closingId . '-discounts');
// 3. Cancelaciones
$this->post('/ingest/cancellations', $this->buildCancellationsPayload($closing), $closingId . '-cancellations');
// 4. Pagos
$this->post('/ingest/payments', $this->buildPaymentsPayload($closing), $closingId . '-payments');
// 5. Resumen de ventas
$this->post('/ingest/sales', $this->buildSalesPayload($closing), $closingId . '-sales');
// 6. El corte en sí — este dispara el procesamiento en RP
$this->post('/ingest/cash-register', $this->buildCashRegisterPayload($closing), $closingId);
}
private function buildCashRegisterPayload(CashRegisterOpening $closing): array
{
return [
'data' => [
'type' => 'cash-register',
'attributes' => [
'branch_identifier' => $this->branchIdentifier,
'closing_type' => 'zout',
'start_at' => $closing->start_at->toIso8601String(),
'end_at' => $closing->end_at->toIso8601String(),
'status' => 'completed',
'summaries' => $closing->summaries->map(fn($s) => [
'type' => $s->type,
'name' => $s->name,
'amount_cents' => (int) ($s->amount * 100),
])->toArray(),
],
'meta' => [
'pos_name' => 'restaurant_controller',
'pos_version' => config('app.version', '1.0.0'),
'pos_variant' => 'full_dining',
'sent_at' => now()->toIso8601String(),
],
],
];
}
/**
* HTTP helper con retry automático (3 intentos).
*/
private function post(string $endpoint, array $payload, string $idempotencyKey): void
{
$attempts = 0;
$maxAttempts = 3;
$delays = [0, 5, 30];
while ($attempts < $maxAttempts) {
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("RestauranteroPro: {$endpoint} enviado correctamente", [
'idempotency_key' => $idempotencyKey,
'status' => $response->status(),
]);
return;
}
Log::warning("RestauranteroPro: {$endpoint} error HTTP {$response->status()}", [
'response' => $response->json(),
]);
} catch (\Exception $e) {
Log::error("RestauranteroPro: {$endpoint} excepción", [
'error' => $e->getMessage(),
'attempt' => $attempts + 1,
]);
}
$attempts++;
if ($attempts < $maxAttempts) {
sleep($delays[$attempts]);
}
}
Log::critical("RestauranteroPro: {$endpoint} falló 3 intentos — idempotency_key: {$idempotencyKey}");
}
}
Configuración en RC (config/restaurantero.php):
<?php
return [
'base_url' => env('RESTAURANTERO_BASE_URL', 'https://api.restauranteropro.com/api/v1'),
'api_key' => env('RESTAURANTERO_API_KEY'),
'branch_identifier' => env('RESTAURANTERO_BRANCH_IDENTIFIER'),
'enabled' => env('RESTAURANTERO_ENABLED', true),
];
Variables de entorno en RC (.env):
RESTAURANTERO_BASE_URL=https://api.restauranteropro.com/api/v1
RESTAURANTERO_API_KEY=rpro_test_xxxxxxxxxxxxxxxxxxxxxxxx
RESTAURANTERO_BRANCH_IDENTIFIER=019e0c31-f390-7185-8bff-f5046c392930
RESTAURANTERO_ENABLED=true
Usa rpro_test_xxx durante las pruebas y rpro_live_xxx cuando el cliente esté listo para producción. La URL base es la misma para ambas keys.
6.8 Checklist de integración
Antes de declarar la integración lista para producción, verifica estos 13 puntos:
7. Webhooks
Restaurantero Pro envía webhooks a tu sistema cuando ocurren eventos importantes. Úsalos para saber cuándo los datos están listos para consultar, sin hacer polling.
Configurar tu webhook
POST /api/v1/developer/webhooks
Content-Type: application/json
{
"url": "https://tu-pos.com/webhooks/restaurantero",
"events": ["snapshot.completed", "alert.triggered", "cash.variance"],
"secret": "tu-secreto-para-verificar-firma"
}
Eventos disponibles
| Evento | Cuándo se dispara | Útil para |
|---|
snapshot.completed | Cubos regenerados después de un Corte Z | Saber que el P&L y KPIs del día ya están disponibles |
alert.triggered | Un KPI cruzó su umbral de alerta | Notificación push al dueño o gerente |
insight.generated | Controllero IA generó el resumen diario | Mostrar el insight en el dashboard del POS |
cash.variance | Faltante/sobrante excede el umbral configurado | Alerta inmediata al gerente |
inventory.low_stock | Ingrediente bajo el mínimo recomendado | Notificación para comprar |
purchase.suggestion_ready | Tiendita generó nueva lista de compra | Notificar al encargado de compras |
Estructura del payload
{
"event": "snapshot.completed",
"branch_identifier": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"timestamp": "2026-05-15T23:52:00Z",
"data": {
"snapshot_type": "daily",
"date": "2026-05-15",
"cubes_regenerated": ["sales", "pnl", "cash_ledger", "menu_engineering"]
}
}
Verificación de firma HMAC-SHA256
Cada webhook incluye el header X-Restaurantero-Signature para verificar que viene de nosotros.
public function handleWebhook(Request $request): Response
{
$signature = $request->header('X-Restaurantero-Signature');
$payload = $request->getContent();
$secret = config('restaurantero.webhook_secret');
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (! hash_equals($expectedSignature, $signature)) {
return response('Firma inválida', 401);
}
$event = $request->json('event');
match ($event) {
'snapshot.completed' => $this->onSnapshotCompleted($request->json()->all()),
'alert.triggered' => $this->onAlertTriggered($request->json()->all()),
'cash.variance' => $this->onCashVariance($request->json()->all()),
'purchase.suggestion_ready' => $this->onSuggestionReady($request->json()->all()),
default => null,
};
return response('OK', 200);
}
Política de reintentos de webhooks
Si tu endpoint no responde 200 OK, Restaurantero Pro reintenta:
Intento 1: Inmediato
Intento 2: 5 segundos después
Intento 3: 30 segundos después
Intento 4: Marcar como fallido — visible en Dev Portal → Webhook Logs
8. Onboarding de sucursal
Proceso actual
Paso 1 — Marco crea la cuenta en el sistema
Group → El grupo restaurantero dueño de la suscripción
└── Brand → La marca o concepto (ej. "Pitic", "Mariscos El Faro")
└── Branch → La sucursal física con su RFC, dirección y timezone
Paso 2 — Se genera la API Key para la sucursal
Desde Ajustes → Integraciones → API Keys, se genera una key para el branch específico:
rpro_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Paso 3 — Se entrega al dueño
El dueño recibe:
- URL del dashboard:
https://api.restauranteropro.com/dashboard
- Email y contraseña para acceder
- API Key de su sucursal:
rpro_live_xxx...
- UUID de su sucursal (
branch_identifier): el UUID que Carlos necesita en la configuración del RC
Paso 4 — Carlos configura RC
RESTAURANTERO_API_KEY=rpro_live_xxxxxxxxxxxxxxxxxxxxxxxx
RESTAURANTERO_BRANCH_IDENTIFIER=b1c2d3e4-f5a6-7890-abcd-ef1234567890
RESTAURANTERO_ENABLED=true
Paso 5 — Verificar que la conexión funciona
GET /api/v1/ping
X-API-Key: rpro_live_xxx...
Response esperado:
{
"data": {
"type": "ping",
"attributes": {
"status": "ok",
"branch_name": "Pitic",
"plan": "pro",
"message": "Conexión establecida correctamente con Restaurantero Pro."
}
}
}
Paso 6 — Primer Corte Z de prueba
Para el primer corte Z usa la API Key rpro_test_xxx. Los datos irán al servidor de producción pero quedarán identificados como prueba. Coordina con el equipo de Restaurantero Pro para limpiarlos después.
Pruebas de integración
- Usa la URL
https://api.restauranteropro.com/api/v1 con tu API Key rpro_test_xxx
- Envía cortes Z de prueba con datos ficticios
- Verifica en el dashboard de Restaurantero Pro que los datos llegaron correctamente
- Cuando todo funcione, cambia a tu API Key
rpro_live_xxx para producción real
9. Catálogo de errores
| Código HTTP | Código interno | Mensaje | Causa | Solución |
|---|
400 | RPRO_ERR_400 | Request malformado | El JSON no tiene la estructura correcta | Verifica la estructura data.type, data.attributes |
401 | RPRO_ERR_401 | API Key faltante o inválida | Header X-API-Key ausente o incorrecto | Verifica el header y la key en el Dev Portal |
403 | RPRO_ERR_403 | Sin permisos para este endpoint | El plan no incluye este módulo | Verifica el plan contratado |
404 | RPRO_ERR_404 | Branch no encontrado | branch_identifier no existe en el sistema | Verifica el UUID del branch |
409 | RPRO_ERR_409 | Idempotency key ya procesada | El request ya fue recibido y procesado | No reintentar — el dato ya está guardado |
422 | RPRO_ERR_422 | Error de validación | Campo requerido faltante o valor inválido | Lee el campo error.field para saber cuál falló |
422 | RPRO_ERR_422_CENTS | Monto no es entero | Se envió un decimal en un campo _cents | Multiplica por 100 y convierte a int |
429 | RPRO_ERR_429 | Rate limit excedido | Demasiados requests por minuto | Espera Retry-After segundos |
500 | RPRO_ERR_500 | Error interno | Falla en el servidor de RP | Reportar a dev@restauranteropro.com con el X-Request-Id |
503 | RPRO_ERR_503 | Servicio temporalmente no disponible | Mantenimiento o sobrecarga | Reintentar con backoff. Ver status.restauranteropro.com |
Ejemplo de respuesta de error 422:
{
"error": {
"code": "RPRO_ERR_422",
"message": "El campo total_sales_cents es requerido y debe ser un entero positivo.",
"field": "data.attributes.total_sales_cents",
"received_value": 534.50,
"request_id": "req_9f3k2m1x",
"docs": "https://docs.restauranteropro.com/errors#RPRO_ERR_422"
}
}
10. Changelog
v1.0.1 — Mayo 2026
POST /api/v1/ingest/orders — Agregado campo waiter_name por línea (requerido para Star Employees). Cada línea ahora genera una fila independiente en BD para queries directas.
- Checklist de integración — Actualizado a 13 puntos, agregados verificaciones de
waiter_name y category_type.
- Sección 6.4 — Mapeo de
OrderTicketLine actualizado con waiter_name y relaciones correctas.
v1.0.0 — Mayo 2026
Release inicial de la API pública de Restaurantero Pro.
Endpoints de ingesta:
POST /api/v1/ingest/cash-register
POST /api/v1/ingest/sales
POST /api/v1/ingest/payments
POST /api/v1/ingest/orders
POST /api/v1/ingest/discounts
POST /api/v1/ingest/cancellations
Endpoints de inteligencia:
GET /api/v1/intelligence/sales/summary
GET /api/v1/intelligence/sales/by-hour
GET /api/v1/intelligence/sales/by-employee
GET /api/v1/intelligence/sales/reconciliation
GET /api/v1/intelligence/cash-flow
GET /api/v1/intelligence/pnl
GET /api/v1/intelligence/food-cost
GET /api/v1/intelligence/labor-cost
GET /api/v1/intelligence/kpis
GET /api/v1/intelligence/alerts
GET /api/v1/intelligence/menu/engineering
GET /api/v1/intelligence/inventory/suggestions
Módulo Live:
GET /api/v1/live/sales/today
GET /api/v1/live/cash/balance
GET /api/v1/live/alerts/active
Webhooks: snapshot.completed, alert.triggered, insight.generated, cash.variance, inventory.low_stock, purchase.suggestion_ready
Restaurantero Pro — API Documentation v1.0.1
Desarrollado por Restaurant Controller · Hermosillo, Sonora, México
Mayo 2026 · Confidencial — Solo para partners autorizados