CHEFMANAGER IA · V3.10.0 · VALIDACIÓN
FOOD COURT FASE 5 (LIQUIDACIONES) + INTERFAZ DE USUARIO BÁSICA
================================================================

METODOLOGÍA

Igual que en las entregas 3.8.0 y 3.9.0: se levantó un servidor PHP real
(`php -S 127.0.0.1:8094`) sobre una copia aislada del paquete
(/tmp/httptest/maestro, nunca el paquete real que se entrega), con
`force_https` desactivado solo en esa copia. Se sembraron datos con
scripts de fixture que fijan `$_SERVER['DOCUMENT_ROOT']` para escribir en
el mismo SQLite que usa el servidor web. Todo el flujo se probó con
peticiones HTTP reales (curl con cookies y cabeceras CSRF) y, para las
tres páginas nuevas, con un navegador real headless (Chromium vía
Playwright) que ejecuta el JavaScript tal como lo haría un usuario. Al
finalizar, se compararon por diff los archivos de la copia probada contra
el paquete real antes de empaquenar, confirmando que son idénticos byte a
byte.

1) MIGRACIÓN SQLITE (hasta v22)

- Instalación nueva desde cero: `migrate()` corre sin errores.
- `PRAGMA integrity_check` → ok.
- `PRAGMA foreign_key_check` → 0 violaciones.
- Conteo de tablas: 180 (178 en la V3.9.0 + food_court_settlements +
  food_court_settlement_operators).
- `food_court_operators` tiene las columnas nuevas `commission_percent`
  (REAL NOT NULL DEFAULT 0) y `rent_amount` (REAL NOT NULL DEFAULT 0).
- Segunda ejecución de `migrate()` sobre la misma base de datos: sin
  errores, integridad ok (idempotente).

2) ESCENARIO DE PRUEBA (Fase 5, backend)

- Superadmin `fc-super@test.local` creado y autenticado.
- Restaurante "Comidas Test B" (id=3, branch=7) creado vía
  POST /restaurants; producto "Salchipapa B" (id=120, precio 25) y
  horario forzado a abierto.
- Patio "Patio Liquidacion" (id=1) creado vía POST /food-courts.
- Operador invitado (restaurant_id=3, branch_id=7) → operator id=1.
- Owner B acepta la invitación: PUT .../operators/1 {"status":"active"} →
  "Te uniste al patio de comidas."
- Superadmin fija el contrato de liquidación: PUT .../operators/1
  {"commission_percent":10,"rent_amount":15} → "Operador actualizado."

BUG ENCONTRADO Y CORREGIDO (ver RELEASE-3.10.0.txt, punto 3a): tras el paso
anterior, dos intentos de pedir a ese operador desde el directorio público
fallaron con "El operador \"Comidas B\" ya no está disponible en este
patio de comidas." Consulta directa a la base de datos confirmó
`food_court_operators.status=''` (vacío) pese a que el paso previo había
respondido con éxito. Se corrigió el cálculo de `$status` en el PUT de
operador (api en app/Services/FoodCourtService.php) y se repitió la
prueba:

- Se reparó manualmente el registro de prueba (`status='active'`) y se
  repitió el mismo PUT de comisión/alquiler (sin enviar "status") con el
  código corregido → la respuesta sigue siendo exitosa y, esta vez,
  `status` permanece en 'active' (verificado con una consulta directa a
  la base de datos, no solo por la respuesta HTTP).
- Se repitió la misma verificación sobre PUT /food-courts/{id} (sin
  enviar "status", solo "description"): el estado del patio permanece
  'active' después del cambio (antes del fix se habría vaciado igual).

3) FLUJO COMPLETO DE LIQUIDACIÓN (HTTP real)

- GET food-court?slug=patio-liquidacion confirma que el operador vuelve a
  aparecer en el directorio público.
- Pedido 1: POST food-court-order (operator_id=1, 1×Salchipapa B) → grupo
  id=1, subpedido order_id=5, subtotal 25.
- Pedido 2: POST food-court-order (mismo operador) → grupo id=2, subpedido
  order_id=6, subtotal 25.
- Pago central sobre el pedido 1: POST
  /food-courts/1/orders/1/payments {"method":"cash","amount":25} →
  aplicado, orders.payment_status='paid' en order_id=5.
- Pago local sobre el pedido 2: owner B abre una caja real
  (POST /cash/open, registro "Caja Principal") y hace
  POST /orders/6/checkout {"payments":[{"method":"cash","amount":25}]} →
  "Venta registrada." (sale_number VTA-...).
- Verificación directa en base de datos del límite Fase 4 (sigue
  vigente): 0 filas en `sales` para el order_id=5 (pago central), 1 fila
  en `sales` para el order_id=6 (checkout propio), 1 movimiento en
  `cash_movements` de la sesión de caja de owner B.
- Generación de liquidación: POST /food-courts/1/settlements
  {"period_start":"2026-08-24","period_end":"2026-08-26"} →
  "Liquidación generada con 1 operador(es)."
- Detalle (GET .../settlements/1), cifras obtenidas y verificadas contra
  el cálculo esperado a mano:
    orders_count=2, gross_sales=50, centrally_collected=25,
    locally_collected=25, commission_rate=10, commission_total=5,
    rent_total=15, net_payable=5, amount_patio_owes_operator=5,
    amount_operator_owes_patio=0.
  (comprobación: net = central - comisión - alquiler = 25 - 5 - 15 = 5;
  como es positivo, el patio le debe 5 al operador.)

4) CASOS NEGATIVOS Y DE BORDE (Fase 5)

- POST .../settlements con period_end anterior a period_start → rechazado
  ("El período indicado no es válido.").
- POST .../settlements sin period_start/period_end → rechazado ("Indica
  el período...").
- PUT .../settlements/1 {"status":"final"} → "Liquidación cerrada."
- Segundo intento de modificar la misma liquidación (PUT con
  {"status":"draft"}) → rechazado ("Esta liquidación ya está cerrada y no
  puede modificarse.").
- Owner B (cuenta de negocio, no de plataforma) intenta generar una
  liquidación del patio → rechazado con 403 ("No tienes permiso para
  administrar este patio de comidas.").
- Owner B intenta fijarse su propia comisión/alquiler vía la autogestión
  (PUT .../operators/1 con su propia sesión, incluyendo
  commission_percent/rent_amount en el cuerpo) → la respuesta es
  exitosa (puede reafirmar su propio "active"), pero comisión y alquiler
  permanecen sin cambio (10 y 15, verificado en base de datos): la rama
  de autogestión, tal como está escrita, nunca toca esas dos columnas.

5) INTERFAZ DE USUARIO (navegador real, Chromium headless vía Playwright)

food-court.php (directorio y pedido):
- Carga con slug=patio-liquidacion, muestra el nombre del patio y 1
  producto disponible.
- Agregar el producto al carrito muestra el botón flotante de carrito.
- Abrir el carrito muestra el nombre del operador y el producto agregado.
- Completar nombre/teléfono y confirmar el pedido muestra el modal de
  éxito con el número de grupo — pedido creado de verdad (confirmado
  luego con una consulta a food_court_orders).

food-court-tracking.php:
- Con el token del pedido recién creado, muestra el nombre del patio y el
  estado "Pendiente", coincidente con la respuesta de la API
  food-court-track para ese mismo token.

food-court-admin.php:
- Sin sesión, muestra el formulario de acceso (no el panel).
- Login como `fc-super@test.local` muestra el panel con el selector de
  patios.
- Al elegir "Patio Liquidacion", la pestaña "Operadores" muestra la tabla
  con el operador y sus columnas de estado/comisión/alquiler.
- La pestaña "Pedidos" lista los pedidos maestros del patio; abrir el
  detalle de uno muestra sus subpedidos, ítems y la sección de pago
  central con el saldo pendiente correcto.
- La pestaña "Liquidaciones" lista la liquidación generada; abrir su
  detalle muestra la tabla por operador con las mismas cifras verificadas
  en el punto 3.
- Cerrando sesión y entrando como `ownerb@test.local` (cuenta de negocio,
  no de plataforma) el panel muestra la vista de negocio ("Tu
  participación en patios de comidas"), no el panel de administración,
  con el patio en estado "active" y el botón "Salir del patio" — nunca
  se le ofrecen los controles de comisión/alquiler ni la gestión de otros
  operadores.
- Sin errores de JavaScript en consola en ninguno de los flujos anteriores
  (se registró explícitamente `pageerror`/`console.error` durante toda la
  sesión de Playwright).

Después de esta ronda de pruebas, se compararon por diff los archivos
modificados/creados de la copia de prueba (/tmp/httptest/maestro) contra
el paquete real (maestro fuente) antes de empaquetar: idénticos byte a
byte en los 12 archivos tocados (2 servicios PHP corregidos, la migración
SQL nueva, Database.php, y las 3 páginas + 5 archivos de assets de la
interfaz nueva).

6) IDENTIDAD Y ESTRUCTURA GENERAL

- Identidad exclusiva ChefManager IA en todo el contenido nuevo.
- `php -l` sin errores en el 100% de los archivos .php del paquete.
- `node --check` sin errores en los 3 archivos .js nuevos.
