CHEFMANAGER IA · V3.11.0 · VALIDACIÓN
COSTEO CON CONVERSIÓN DE UNIDADES + CORRECCIÓN DE BUGS DE TERNARIO
================================================================================

METODOLOGÍA

Igual que en entregas anteriores: servidor PHP real (`php -S
127.0.0.1:8095`) sobre una copia aislada del paquete (/tmp/httptest/
maestro, nunca el paquete real que se entrega), con `force_https`
desactivado solo en esa copia. Datos sembrados con scripts de fixture que
fijan `$_SERVER['DOCUMENT_ROOT']` para escribir en el mismo SQLite que
usa el servidor web (gotcha conocido: CLI y `php -S` resuelven la ruta de
la base de datos de forma distinta según DOCUMENT_ROOT). Todo el flujo se
probó con peticiones HTTP reales (curl con cookies y cabecera CSRF). Se
verificó sintaxis de los archivos PHP modificados con `php -l` y del
JavaScript con `node --check` antes de considerar cualquier cambio
completo. Al finalizar, se comparó por diff la copia probada contra el
paquete real antes de empaquetar.

1) COSTEO: CONVERSIÓN DE UNIDADES

Escenario: restaurante "Costing Test" (id=4), ingrediente "Lomo de res"
(id=121, unit=kg, cost_per_unit=20), receta "Lomo a la plancha 250g"
(id=7, yield=1), producto "Lomo a la plancha" (id=307, precio=20,
food_cost objetivo=30%).

a) Receta cargada con línea de ingrediente en unidad distinta (g) sin
   conversión configurada:
   - POST /recipes con línea {"ingredient_id":121,"quantity":250,
     "unit":"g"} (ingrediente está en kg) → rechazado con 409:
     "Falta configurar la conversión de unidades de g a kg para calcular
     el costo de esta receta." Nada se guardó (confirmado: la receta no
     aparece en el listado tras el intento).

b) Se configura la conversión: POST /unit-conversions
   {"from_unit":"g","to_unit":"kg","factor":0.001} → 201.

c) Reintento de creación de la misma receta → 201 (ahora sí se guarda).

d) Cálculo de costo verificado numéricamente:
   - 250 g × 0.001 (factor g→kg) = 0.25 kg de "Lomo de res".
   - 0.25 kg × $20.00/kg = $5.00 → costo de la receta (yield=1).
   - GET /recipes confirmó `cost_per_unit: 5.00` exacto, sin `cost_error`.
   - GET /costing/products, producto "Lomo a la plancha" (precio=20,
     food_cost objetivo=30%): `recipe_cost: 5.00`, `food_cost_percent:
     25.00` (5/20), `gross_margin: 15.00`, `suggested_price: 16.67`
     ($5.00 / 0.30, redondeado), `cost_status: "healthy"` (25% está por
     debajo del 30% objetivo). Todos los números verificados a mano
     coinciden exactamente con la respuesta del endpoint.

e) Degradación controlada: se borró la conversión (DELETE
   /unit-conversions/1) y se repitieron las lecturas SIN volver a crear
   la receta (para simular una conversión que existía y fue removida
   después):
   - GET /recipes → la receta aparece con `cost_error: "Falta configurar
     la conversión de unidades de g a kg..."` y sin `cost_per_unit`
     numérico (200, no 500).
   - GET /costing/products → el producto aparece con `cost_status:
     "unit_error"`, `cost_error` con el mismo mensaje, `recipe_cost`,
     `food_cost_percent`, `gross_margin` y `suggested_price` en null/"—"
     en vez de un número inventado; el resumen reporta
     `unit_conversion_errors: 1`.
   - Confirmado que este fallo NO bloquea operaciones no relacionadas:
     un PUT posterior sobre un ingrediente distinto (ver punto 2) se
     probó justo después y respondió con éxito, no con un error
     arrastrado del intento fallido de recalcular el costo de esta
     receta (el snapshot de costo captura la excepción y sigue).

2) CORRECCIÓN: BUG DE TERNARIO EN PUT DE INGREDIENTES

Reproducción del bug ANTES de la corrección (evidencia capturada en
`storage/logs/error.log` de la corrida): PUT /ingredients/121 con body
`{"name":"Lomo de res","unit":"kg","cost_per_unit":22}` (sin `status`) →
500 "Error de base de datos.", log interno:
  `PDO SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL
  constraint failed: ingredients.status`
y advertencia previa `PHP Warning: Undefined array key "status"`.

Tras aplicar la corrección (captura de `$b['status']??'active'` en una
variable intermedia antes del `in_array`), se repitió EXACTAMENTE la
misma petición contra el mismo registro:
  - Respuesta: `{"ok":true,"message":"Ingrediente actualizado.",
    "data":null}` (200, sin warnings en el log del servidor).
  - Consulta directa a SQLite tras la petición: `id=121, name="Lomo de
    res", unit="kg", cost_per_unit=22, status="active"` — el status se
    conserva correctamente, no queda en null ni provoca el error de
    integridad.

3) CORRECCIÓN: MISMO BUG EN app/Services/OperationsExtra.php
   (mantenimiento de equipos)

Se creó un equipo de prueba ("Horno test", criticality=high,
status=operational) y una orden de trabajo (work_order_number=WO-TEST-1,
maintenance_type=corrective, priority=high, status=open, summary="Fuga de
gas") sobre el restaurante de prueba.

PUT /maintenance-work-orders/{id} enviando ÚNICAMENTE
`{"summary":"Fuga de gas reparada parcialmente"}` (sin status, priority
ni maintenance_type) → 200 "Orden de trabajo actualizada.". Consulta
directa a la base de datos tras la petición:
  `status="open", priority="high", maintenance_type="corrective",
  summary="Fuga de gas reparada parcialmente"`
— los tres campos no enviados conservan su valor original (antes de la
corrección, los tres habrían quedado en `null` por el mismo mecanismo de
bug que en ingredientes, con el agravante de que aquí no hay restricción
NOT NULL que lo delate con un error: el dato simplemente se habría
corrompido en silencio).

Las otras tres instancias corregidas en el mismo archivo
(maintenance-plans, maintenance-providers, maintenance-parts) comparten
exactamente el mismo patrón de código y la misma corrección aplicada
(captura en variable intermedia antes del `in_array`); se verificaron por
lectura de código y `php -l` tras la corrección, dado que el mecanismo
del bug y de la corrección es idéntico byte a byte al ya probado en vivo
en ingredientes y en la orden de trabajo. Se registran como corregidas
con la misma confianza que las otras dos, verificadas en vivo mediante
HTTP real.

4) SINTAXIS Y EMPAQUETADO

- `php -l api/index.php` → sin errores.
- `php -l app/Services/OperationsExtra.php` → sin errores.
- `node --check public/assets/app.js` → sin errores (sin cambios de JS
  en esta ronda respecto a lo ya validado; se re-verifica por rutina).
- Instalación limpia desde cero sobre el paquete final (MAESTRO):
  `migrate()` corre sin errores, `PRAGMA integrity_check` → ok,
  `PRAGMA foreign_key_check` → 0 violaciones. La nueva columna
  `recipe_ingredients.unit` existe y acepta NULL (confirmado por
  `PRAGMA table_info`).
- Diff de la copia probada (/tmp/httptest/maestro) contra el paquete
  final antes de empaquetar: archivos modificados en esta ronda
  idénticos byte a byte.
