// ===== Persistance : adaptateur localStorage (hors environnement artifact) =====
if (!window.storage) {
  window.storage = {
    async get(key) { const v = localStorage.getItem("mizenpro:" + key); if (v === null) throw new Error("not found"); return { key, value: v }; },
    async set(key, value) { localStorage.setItem("mizenpro:" + key, value); return { key, value }; },
    async delete(key) { localStorage.removeItem("mizenpro:" + key); return { key, deleted: true }; },
    async list(prefix) { return { keys: Object.keys(localStorage).filter(k => k.startsWith("mizenpro:" + (prefix || ""))).map(k => k.slice(9)) }; },
  };
}

const { useState, useRef, useEffect } = React;

// ============ CHARTE CHEF AI ============
const C = {
  creme: "#F7F2E6",
  cremeFonce: "#EFE7D3",
  or: "#C8962A",
  orClair: "#E6CC85",
  brun: "#352718",
  brunMoyen: "#6F5843",
  rouille: "#9A3F1F",
  vert: "#4C7046",
  blanc: "#FFFEFA",
};

// ============ DONNÉES DEMO ============
let FICHES = [
  // ===== ENTRÉES =====
  {
    id: 1, nom: "Accras de morue", description: "Beignets de morue croustillants aux herbes pays, servis brûlants", categorie: "Entrée", portions: 40,
    coutPortion: 0.32, prixVente: 1.2, tempsPrepa: "30 min", tempsCuisson: "15 min",
    ingredients: [
      { nom: "Morue dessalée", qte: "800 g", cout: 7.2 },
      { nom: "Farine T55", qte: "500 g", cout: 0.6 },
      { nom: "Levure chimique", qte: "11 g", cout: 0.15 },
      { nom: "Cive, persil, ail, piment", qte: "PM", cout: 1.4 },
      { nom: "Huile de friture", qte: "2 L", cout: 3.6 },
    ],
    etapes: [
      "Effilocher la morue dessalée 24h et pochée",
      "Préparer la pâte : farine, levure, eau, aromates finement hachés",
      "Incorporer la morue, laisser reposer 30 min à température ambiante",
      "Frire à 175°C par petites quenelles jusqu'à coloration dorée",
      "Égoutter sur papier absorbant, servir brûlant",
    ],
    allergenes: ["Gluten", "Poisson"],
    dressage: [{ element: "Accras", qte: "5 pièces (≈ 90 g)" }, { element: "Sauce chien en pipette", qte: "20 g" }, { element: "Quartier de citron vert", qte: "1 pièce" }, { element: "Pluche de persil", qte: "PM" }],
  },
  {
    id: 2, nom: "Boudin créole", description: "Le boudin antillais traditionnel, relevé au piment végétarien et bois d'Inde", categorie: "Entrée", portions: 30,
    coutPortion: 0.55, prixVente: 1.8, tempsPrepa: "50 min", tempsCuisson: "25 min",
    ingredients: [
      { nom: "Sang de porc", qte: "1 L", cout: 4.5 },
      { nom: "Pain rassis", qte: "400 g", cout: 0.8 },
      { nom: "Boyaux naturels", qte: "4 m", cout: 3.2 },
      { nom: "Cive, oignon, ail, bois d'Inde", qte: "PM", cout: 2.1 },
      { nom: "Piment végétarien + piment fort", qte: "150 g", cout: 1.6 },
      { nom: "Lait, épices, sel", qte: "PM", cout: 1.3 },
    ],
    etapes: [
      "Tremper le pain dans le lait, hacher finement tous les aromates",
      "Mélanger sang, pain essoré, aromates et épices, assaisonner relevé",
      "Embosser sans trop serrer, nouer en chapelet",
      "Pocher à frémissement (85°C, jamais bouillir) 20-25 min",
      "Piquer pour vérifier : le jus doit être brun clair. Refroidir rapidement (cellule)",
    ],
    allergenes: ["Gluten", "Lait"],
    dressage: [{ element: "Tronçons de boudin", qte: "3 pièces (≈ 120 g)" }, { element: "Pointe de moutarde créole", qte: "10 g" }, { element: "Mini-salade de cive", qte: "15 g" }, { element: "Pain grillé", qte: "1 tranche" }],
  },
  {
    id: 3, nom: "Féroce d'avocat", description: "Avocat pays pilé, morue grillée et farine de manioc — frais et corsé", categorie: "Entrée", portions: 12,
    coutPortion: 1.15, prixVente: 7.5, tempsPrepa: "35 min", tempsCuisson: "10 min",
    ingredients: [
      { nom: "Avocats pays mûrs", qte: "4 pièces", cout: 6.0 },
      { nom: "Morue dessalée", qte: "500 g", cout: 4.5 },
      { nom: "Farine de manioc", qte: "300 g", cout: 1.4 },
      { nom: "Citron vert", qte: "2 pièces", cout: 0.6 },
      { nom: "Piment, ail, huile", qte: "PM", cout: 1.3 },
    ],
    etapes: [
      "Griller légèrement la morue dessalée puis l'effilocher",
      "Écraser la chair d'avocat au pilon avec citron vert",
      "Incorporer morue, farine de manioc, ail pilé et piment",
      "Travailler jusqu'à consistance ferme et homogène",
      "Façonner en quenelles, servir frais avec un trait d'huile",
    ],
    allergenes: ["Poisson"],
    dressage: [{ element: "Quenelles de féroce", qte: "2 pièces (≈ 140 g)" }, { element: "Effiloché de morue grillée", qte: "20 g" }, { element: "Trait d'huile au piment", qte: "5 g" }, { element: "Zeste de citron vert", qte: "PM" }],
  },
  {
    id: 4, nom: "Crabe farci", description: "Chair de crabe aux aromates, gratinée dans sa carapace", categorie: "Entrée", portions: 12,
    coutPortion: 2.4, prixVente: 9.5, tempsPrepa: "1h", tempsCuisson: "20 min",
    ingredients: [
      { nom: "Crabes de terre (ou tourteaux)", qte: "6 pièces", cout: 21.0 },
      { nom: "Mie de pain + lait", qte: "250 g", cout: 0.9 },
      { nom: "Cive, ail, persil, piment", qte: "PM", cout: 1.8 },
      { nom: "Chapelure", qte: "150 g", cout: 0.5 },
      { nom: "Beurre, citron vert, épices", qte: "PM", cout: 1.6 },
    ],
    etapes: [
      "Cuire les crabes au court-bouillon épicé 15 min, décortiquer en gardant les carapaces",
      "Faire revenir la chair avec aromates hachés et piment",
      "Lier avec la mie de pain trempée, rectifier au citron vert",
      "Garnir les carapaces, chapelure et noisette de beurre",
      "Gratiner au four 200°C jusqu'à belle coloration",
    ],
    allergenes: ["Crustacés", "Gluten", "Lait"],
    dressage: [{ element: "Carapace farcie gratinée", qte: "1 pièce (≈ 130 g)" }, { element: "Mesclun assaisonné", qte: "30 g" }, { element: "Demi-citron vert bridé", qte: "1 pièce" }],
  },
  {
    id: 5, nom: "Soupe z'habitants", description: "Velouté de légumes pays et feuilles de calalou, au lard fumé", categorie: "Entrée", portions: 15,
    coutPortion: 1.1, prixVente: 8.0, tempsPrepa: "40 min", tempsCuisson: "1h15",
    ingredients: [
      { nom: "Légumes pays (giraumon, igname, madère)", qte: "2 kg", cout: 7.5 },
      { nom: "Feuilles d'épinard pays / calalou", qte: "600 g", cout: 2.8 },
      { nom: "Lard fumé ou queue de cochon", qte: "400 g", cout: 3.6 },
      { nom: "Cive, ail, bois d'Inde, persil", qte: "PM", cout: 1.5 },
      { nom: "Piment végétarien", qte: "4 pièces", cout: 1.0 },
    ],
    etapes: [
      "Blanchir la viande salée pour la dessaler, puis la faire revenir",
      "Ajouter les légumes pays en morceaux et mouiller largement",
      "Cuire 1h à frémissement avec le bouquet aromatique",
      "Incorporer les feuilles vertes 15 min avant la fin",
      "Écraser partiellement au moulin, ajuster l'assaisonnement",
    ],
    allergenes: ["Aucun allergène majeur"],
    dressage: [{ element: "Soupe en assiette creuse", qte: "300 ml" }, { element: "Brunoise de légumes pays", qte: "25 g" }, { element: "Lard fumé croustillant", qte: "15 g" }, { element: "Feuille de calalou frite", qte: "1 pièce" }],
  },

  // ===== PLATS =====
  {
    id: 6, nom: "Colombo de poulet", description: "Poulet mariné mijoté aux épices colombo, christophines et lait de coco", categorie: "Plat", portions: 10,
    coutPortion: 2.85, prixVente: 14.5, tempsPrepa: "45 min", tempsCuisson: "1h10",
    ingredients: [
      { nom: "Cuisses de poulet", qte: "2,5 kg", cout: 11.25 },
      { nom: "Poudre de colombo", qte: "80 g", cout: 1.6 },
      { nom: "Christophines", qte: "1 kg", cout: 3.8 },
      { nom: "Pommes de terre", qte: "1,2 kg", cout: 1.9 },
      { nom: "Lait de coco", qte: "400 ml", cout: 2.1 },
      { nom: "Aromates (ail, oignon, cive, thym)", qte: "PM", cout: 1.5 },
      { nom: "Citron vert", qte: "3 pièces", cout: 0.9 },
      { nom: "Huile, sel, piment", qte: "PM", cout: 0.8 },
    ],
    etapes: [
      "Découper et mariner le poulet (citron vert, ail, colombo) — 30 min minimum",
      "Faire revenir les morceaux dans l'huile chaude jusqu'à coloration",
      "Ajouter oignons, cive, puis mouiller à hauteur",
      "Incorporer légumes et lait de coco, cuire 1h10 à frémissement",
      "Rectifier l'assaisonnement, finir au jus de citron vert",
    ],
    allergenes: ["Aucun allergène majeur"],
    dressage: [{ element: "Morceaux de poulet nappés", qte: "250 g" }, { element: "Christophines & pommes de terre", qte: "120 g" }, { element: "Riz créole moulé", qte: "150 g" }, { element: "Sauce colombo", qte: "60 g" }, { element: "Cive ciselée & citron vert", qte: "PM" }],
  },
  {
    id: 7, nom: "Court-bouillon de poisson", description: "Vivaneau poché dans une sauce tomatée au roucou et citron vert", categorie: "Plat", portions: 8,
    coutPortion: 3.6, prixVente: 17.0, tempsPrepa: "40 min + marinade", tempsCuisson: "25 min",
    ingredients: [
      { nom: "Vivaneau ou dorade (darnes)", qte: "2 kg", cout: 22.0 },
      { nom: "Tomates + concentré", qte: "600 g", cout: 2.2 },
      { nom: "Citron vert, ail, piment", qte: "PM", cout: 1.5 },
      { nom: "Cive, oignon, persil, bois d'Inde", qte: "PM", cout: 1.6 },
      { nom: "Roucou (huile rougie)", qte: "5 cl", cout: 0.9 },
    ],
    etapes: [
      "Mariner le poisson 1h : citron vert, ail pilé, piment, sel",
      "Faire revenir oignon et cive dans l'huile au roucou",
      "Ajouter tomates concassées et concentré, compoter 10 min",
      "Déposer les darnes, mouiller à mi-hauteur, couvrir",
      "Cuire 15 min à frémissement, finir persil et citron vert",
    ],
    allergenes: ["Poisson"],
    dressage: [{ element: "Darne de vivaneau", qte: "220 g" }, { element: "Sauce tomatée au roucou", qte: "80 g" }, { element: "Riz blanc", qte: "150 g" }, { element: "Persil plat & citron vert", qte: "PM" }],
  },
  {
    id: 8, nom: "Fricassée de chatrou", description: "Poulpe fondant mijoté aux tomates, servi avec riz et haricots rouges", categorie: "Plat", portions: 8,
    coutPortion: 3.9, prixVente: 18.5, tempsPrepa: "50 min", tempsCuisson: "1h15",
    ingredients: [
      { nom: "Chatrou (poulpe) nettoyé", qte: "2,4 kg", cout: 24.0 },
      { nom: "Tomates", qte: "500 g", cout: 1.8 },
      { nom: "Oignon, ail, cive, thym", qte: "PM", cout: 1.5 },
      { nom: "Vin blanc sec", qte: "20 cl", cout: 1.2 },
      { nom: "Citron vert, piment, bois d'Inde", qte: "PM", cout: 1.4 },
      { nom: "Riz blanc + haricots rouges (accomp.)", qte: "800 g", cout: 1.3 },
    ],
    etapes: [
      "Battre et citronner le chatrou, le détailler en tronçons",
      "Le faire raidir à sec pour rendre son eau, réserver",
      "Suer les aromates, ajouter tomates et chatrou, déglacer au vin blanc",
      "Mijoter à couvert 1h-1h15 jusqu'à tendreté (pointe de couteau)",
      "Réduire la sauce, rectifier piment et citron vert, servir avec riz-haricots rouges",
    ],
    allergenes: ["Mollusques"],
    dressage: [{ element: "Chatrou en sauce", qte: "220 g" }, { element: "Riz & haricots rouges", qte: "180 g" }, { element: "Réduction de sauce", qte: "50 g" }, { element: "Piment végétarien confit", qte: "1 pièce" }],
  },
  {
    id: 9, nom: "Matété de crabe", description: "Riz mijoté aux crabes de terre et lard fumé — le plat de Pâques antillais", categorie: "Plat", portions: 10,
    coutPortion: 3.2, prixVente: 16.5, tempsPrepa: "1h", tempsCuisson: "45 min",
    ingredients: [
      { nom: "Crabes de terre", qte: "10 pièces", cout: 24.0 },
      { nom: "Riz long grain", qte: "1 kg", cout: 1.6 },
      { nom: "Lard fumé", qte: "300 g", cout: 2.7 },
      { nom: "Tomates, oignon, ail, cive", qte: "PM", cout: 2.0 },
      { nom: "Bois d'Inde, piment, citron vert", qte: "PM", cout: 1.7 },
    ],
    etapes: [
      "Nettoyer et tronçonner les crabes, conserver les pinces",
      "Faire revenir lard et crabes jusqu'à belle coloration",
      "Ajouter aromates et tomates, mouiller à 2 fois le volume du riz",
      "Verser le riz, couvrir, cuire 20-25 min à feu doux sans remuer",
      "Détendre à la fourchette, piment et citron vert au moment",
    ],
    allergenes: ["Crustacés"],
    dressage: [{ element: "Riz au crabe dressé à l'assiette", qte: "300 g" }, { element: "Pinces de crabe", qte: "2 pièces" }, { element: "Jus de cuisson", qte: "30 g" }, { element: "Citron vert", qte: "1 quartier" }],
  },
  {
    id: 10, nom: "Poulet boucané", description: "Poulet fermier fumé à la canne, sauce chien maison", categorie: "Plat", portions: 10,
    coutPortion: 2.6, prixVente: 15.0, tempsPrepa: "30 min + 12h marinade", tempsCuisson: "1h30",
    ingredients: [
      { nom: "Poulet entier fermier", qte: "2 × 1,6 kg", cout: 16.0 },
      { nom: "Marinade (citron vert, ail, piment, cive)", qte: "PM", cout: 2.2 },
      { nom: "Canne à sucre / bois pour fumage", qte: "PM", cout: 2.5 },
      { nom: "Sauce chien (cive, piment, citron, huile)", qte: "40 cl", cout: 2.4 },
      { nom: "Riz créole (accomp.)", qte: "1 kg", cout: 1.6 },
    ],
    etapes: [
      "Mariner les poulets crapaudine 12h (citron, ail, piment, sel)",
      "Préparer le boucan : braises douces + canne/bois vert pour la fumée",
      "Cuire à couvert en fumée indirecte 1h30, retourner régulièrement",
      "Contrôler à cœur : 74°C minimum (HACCP)",
      "Découper, napper de sauce chien, servir avec riz créole",
    ],
    allergenes: ["Aucun allergène majeur"],
    dressage: [{ element: "Demi-poulet boucané découpé", qte: "300 g" }, { element: "Sauce chien", qte: "50 g" }, { element: "Riz créole", qte: "150 g" }, { element: "Salade fraîcheur", qte: "40 g" }],
  },
  {
    id: 11, nom: "Ragoût de cochon", description: "Épaule de porc confite aux clous de girofle, ignames vapeur", categorie: "Plat", portions: 12,
    coutPortion: 2.3, prixVente: 14.0, tempsPrepa: "40 min + marinade", tempsCuisson: "1h45",
    ingredients: [
      { nom: "Épaule de porc", qte: "3 kg", cout: 18.0 },
      { nom: "Citron vert, ail, oignon, cive", qte: "PM", cout: 2.0 },
      { nom: "Clous de girofle, bois d'Inde", qte: "PM", cout: 0.8 },
      { nom: "Piment végétarien + fort", qte: "PM", cout: 1.2 },
      { nom: "Ignames (accomp.)", qte: "2 kg", cout: 5.5 },
    ],
    etapes: [
      "Mariner le porc en cubes la veille (citron, ail, girofle)",
      "Saisir vivement les morceaux jusqu'à caramélisation",
      "Ajouter oignons et cive, mouiller à hauteur",
      "Mijoter 1h45 à couvert, sauce nappante et viande confite",
      "Servir avec ignames vapeur — incontournable des fêtes de fin d'année",
    ],
    allergenes: ["Aucun allergène majeur"],
    dressage: [{ element: "Cubes de porc confits & sauce", qte: "240 g" }, { element: "Ignames vapeur", qte: "150 g" }, { element: "Jus réduit", qte: "40 g" }, { element: "Persil haché", qte: "PM" }],
  },
  {
    id: 12, nom: "Blaff de poisson", description: "Poisson poché minute dans un bouillon citronné au bonda man jak", categorie: "Plat", portions: 8,
    coutPortion: 3.4, prixVente: 16.5, tempsPrepa: "30 min + marinade", tempsCuisson: "15 min",
    ingredients: [
      { nom: "Poissons coffres ou vivaneau", qte: "2 kg", cout: 21.0 },
      { nom: "Citron vert", qte: "6 pièces", cout: 1.8 },
      { nom: "Ail, cive, bois d'Inde, thym", qte: "PM", cout: 1.5 },
      { nom: "Piment bonda man jak (entier)", qte: "2 pièces", cout: 0.6 },
      { nom: "Oignon, clous de girofle", qte: "PM", cout: 0.7 },
    ],
    etapes: [
      "Mariner le poisson 1h en citronnade aillée et pimentée",
      "Porter à ébullition un bouillon : eau, aromates, piment entier (non percé !)",
      "Plonger le poisson dans le bouillon bouillant — le 'blaff' du contact",
      "Pocher 10-12 min à frémissement, ne jamais crever le piment",
      "Servir aussitôt dans le bouillon avec citron vert frais",
    ],
    allergenes: ["Poisson"],
    dressage: [{ element: "Poisson entier ou darne", qte: "250 g" }, { element: "Bouillon de blaff servi à l'assiette creuse", qte: "200 ml" }, { element: "Légumes du bouillon", qte: "60 g" }, { element: "Citron vert", qte: "2 quartiers" }],
  },
  {
    id: 13, nom: "Dombrés aux ouassous", description: "Petites boules de pâte et grosses crevettes en sauce coco", categorie: "Plat", portions: 8,
    coutPortion: 3.8, prixVente: 18.0, tempsPrepa: "45 min", tempsCuisson: "40 min",
    ingredients: [
      { nom: "Ouassous (grosses crevettes)", qte: "1,6 kg", cout: 22.4 },
      { nom: "Farine T55 (dombrés)", qte: "600 g", cout: 0.7 },
      { nom: "Tomates, oignon, ail, cive", qte: "PM", cout: 2.0 },
      { nom: "Lait de coco", qte: "400 ml", cout: 2.1 },
      { nom: "Citron vert, piment, persil", qte: "PM", cout: 1.4 },
    ],
    etapes: [
      "Façonner les dombrés : farine + eau salée, petites boules fermes",
      "Faire revenir les ouassous décortiqués (garder têtes pour le fond)",
      "Réaliser la sauce : aromates, tomates, jus des têtes, lait de coco",
      "Plonger les dombrés dans la sauce, cuire 25 min — ils doivent gonfler",
      "Remettre les ouassous 5 min, rectifier, servir bien nappé",
    ],
    allergenes: ["Crustacés", "Gluten"],
    dressage: [{ element: "Dombrés en sauce", qte: "150 g" }, { element: "Ouassous décortiqués", qte: "5 pièces (≈ 120 g)" }, { element: "Sauce coco", qte: "80 g" }, { element: "Têtes d'ouassous rôties (décor)", qte: "2 pièces" }],
  },

  // ===== ACCOMPAGNEMENTS =====
  {
    id: 14, nom: "Gratin de christophine", description: "Christophines fondantes en béchamel muscadée, bien gratinées", categorie: "Accompagnement", portions: 12,
    coutPortion: 0.95, prixVente: 5.5, tempsPrepa: "35 min", tempsCuisson: "30 min",
    ingredients: [
      { nom: "Christophines", qte: "2,5 kg", cout: 6.0 },
      { nom: "Béchamel (beurre, farine, lait)", qte: "80 cl", cout: 2.2 },
      { nom: "Fromage râpé", qte: "250 g", cout: 2.3 },
      { nom: "Oignon, ail, cive, muscade", qte: "PM", cout: 0.9 },
    ],
    etapes: [
      "Cuire les christophines à l'eau salée, évider et écraser la chair",
      "Faire revenir la chair avec les aromates, bien dessécher",
      "Lier à la béchamel muscadée, rectifier",
      "Garnir les barques (ou plat), fromage râpé",
      "Gratiner 25-30 min à 190°C",
    ],
    allergenes: ["Gluten", "Lait"],
    dressage: [{ element: "Part de gratin (ou barque)", qte: "180 g" }, { element: "Gratiné doré", qte: "—" }, { element: "Pluche de persil", qte: "PM" }],
  },
  {
    id: 15, nom: "Riz créole & haricots rouges", description: "L'accompagnement emblématique, haricots mijotés au bois d'Inde", categorie: "Accompagnement", portions: 15,
    coutPortion: 0.45, prixVente: 4.0, tempsPrepa: "15 min + trempage", tempsCuisson: "1h30",
    ingredients: [
      { nom: "Haricots rouges secs", qte: "600 g", cout: 1.8 },
      { nom: "Riz long grain", qte: "1,2 kg", cout: 1.9 },
      { nom: "Lard fumé (facultatif)", qte: "200 g", cout: 1.8 },
      { nom: "Cive, ail, bois d'Inde, thym", qte: "PM", cout: 1.2 },
    ],
    etapes: [
      "Tremper les haricots 12h, cuire 1h15 avec le bouquet aromatique",
      "Faire revenir lard et aromates, ajouter les haricots et leur jus",
      "Cuire le riz créole à l'eau salée, grains détachés",
      "Servir le riz nappé de haricots en sauce — base de tout plat créole",
    ],
    allergenes: ["Aucun allergène majeur"],
    dressage: [{ element: "Riz moulé au bol", qte: "150 g" }, { element: "Haricots rouges en sauce", qte: "100 g" }, { element: "Cive ciselée", qte: "PM" }],
  },

  // ===== DESSERTS =====
  {
    id: 16, nom: "Flan coco", description: "Flan onctueux au lait de coco et caramel, parfumé muscade et citron vert", categorie: "Dessert", portions: 12,
    coutPortion: 0.95, prixVente: 6.5, tempsPrepa: "20 min", tempsCuisson: "50 min",
    ingredients: [
      { nom: "Lait concentré sucré", qte: "800 g", cout: 3.4 },
      { nom: "Lait de coco", qte: "800 ml", cout: 4.2 },
      { nom: "Œufs", qte: "8 pièces", cout: 2.4 },
      { nom: "Sucre (caramel)", qte: "200 g", cout: 0.3 },
      { nom: "Vanille, muscade, zeste citron vert", qte: "PM", cout: 1.1 },
    ],
    etapes: [
      "Réaliser un caramel à sec, chemiser le moule",
      "Mélanger laits, œufs battus et épices sans incorporer d'air",
      "Cuire au bain-marie 50 min à 150°C",
      "Refroidir en cellule, réserver 12h au froid avant démoulage",
    ],
    allergenes: ["Œuf", "Lait"],
    dressage: [{ element: "Part de flan démoulée", qte: "110 g" }, { element: "Caramel coulant", qte: "20 g" }, { element: "Copeaux de coco torréfiés", qte: "5 g" }, { element: "Zeste de citron vert", qte: "PM" }],
  },
  {
    id: 17, nom: "Blanc-manger coco", description: "Entremets coco-vanille glacé, léger comme un alizé", categorie: "Dessert", portions: 10,
    coutPortion: 0.85, prixVente: 6.0, tempsPrepa: "20 min", tempsCuisson: "10 min",
    ingredients: [
      { nom: "Lait de coco", qte: "1 L", cout: 5.2 },
      { nom: "Lait concentré sucré", qte: "400 g", cout: 1.7 },
      { nom: "Gélatine (ou agar-agar)", qte: "12 g", cout: 0.6 },
      { nom: "Vanille, cannelle, zeste citron vert", qte: "PM", cout: 1.0 },
    ],
    etapes: [
      "Chauffer les laits avec vanille et cannelle sans bouillir",
      "Incorporer la gélatine hydratée hors du feu",
      "Couler en moules ou verrines, filmer au contact",
      "Prise au froid 4h minimum, démouler et zester au service",
    ],
    allergenes: ["Lait"],
    dressage: [{ element: "Dôme ou verrine", qte: "120 g" }, { element: "Coulis exotique (mangue/goyave)", qte: "25 g" }, { element: "Cannelle poudrée", qte: "PM" }, { element: "Feuille de menthe", qte: "1 pièce" }],
  },
  {
    id: 18, nom: "Tourment d'amour", description: "La tartelette des Saintes : coco confit et génoise au rhum vieux", categorie: "Dessert", portions: 16,
    coutPortion: 0.7, prixVente: 4.5, tempsPrepa: "45 min", tempsCuisson: "35 min",
    ingredients: [
      { nom: "Pâte brisée (farine, beurre)", qte: "800 g", cout: 2.4 },
      { nom: "Confiture de coco", qte: "500 g", cout: 4.0 },
      { nom: "Génoise (œufs, sucre, farine)", qte: "500 g", cout: 2.6 },
      { nom: "Vanille, rhum vieux, cannelle", qte: "PM", cout: 2.2 },
    ],
    etapes: [
      "Foncer les moules individuels de pâte brisée",
      "Garnir d'une couche généreuse de confiture de coco",
      "Couler l'appareil à génoise parfumé vanille-rhum",
      "Cuire 30-35 min à 170°C — la génoise doit rester moelleuse",
      "Refroidir sur grille — la tradition des Saintes",
    ],
    allergenes: ["Gluten", "Œuf", "Lait"],
    dressage: [{ element: "Tartelette", qte: "1 pièce (≈ 90 g)" }, { element: "Sucre glace", qte: "PM" }, { element: "Quenelle de crème fouettée au rhum", qte: "25 g" }],
  },
  {
    id: 19, nom: "Bananes flambées au rhum vieux", description: "Bananes caramélisées, flambées en salle au rhum AOC Martinique", categorie: "Dessert", portions: 8,
    coutPortion: 1.25, prixVente: 7.5, tempsPrepa: "10 min", tempsCuisson: "10 min",
    ingredients: [
      { nom: "Bananes plantain mûres (ou figues-pommes)", qte: "8 pièces", cout: 3.2 },
      { nom: "Beurre", qte: "120 g", cout: 1.1 },
      { nom: "Sucre de canne", qte: "150 g", cout: 0.4 },
      { nom: "Rhum vieux agricole AOC Martinique", qte: "15 cl", cout: 4.2 },
      { nom: "Cannelle, vanille, citron vert", qte: "PM", cout: 1.1 },
    ],
    etapes: [
      "Fendre les bananes en deux dans la longueur",
      "Les dorer au beurre mousseux, saupoudrer de sucre de canne",
      "Caraméliser légèrement, parfumer cannelle et vanille",
      "Flamber au rhum vieux en salle (effet garanti) — sécurité : hotte éteinte, recul client",
      "Servir aussitôt, zeste de citron vert",
    ],
    allergenes: ["Lait"],
    dressage: [{ element: "Demi-bananes caramélisées", qte: "2 pièces" }, { element: "Caramel au rhum", qte: "40 g" }, { element: "Boule de sorbet coco (option)", qte: "50 g" }, { element: "Zeste de citron vert & cannelle", qte: "PM" }],
  },
  {
    id: 20, nom: "Sorbet coco au bâton", description: "Le sorbet coco traditionnel, turbiné minute aux épices douces", categorie: "Dessert", portions: 14,
    coutPortion: 0.65, prixVente: 5.0, tempsPrepa: "30 min", tempsCuisson: "Sans cuisson",
    ingredients: [
      { nom: "Lait de coco frais", qte: "1,2 L", cout: 6.2 },
      { nom: "Lait concentré sucré", qte: "300 g", cout: 1.3 },
      { nom: "Sucre de canne", qte: "120 g", cout: 0.35 },
      { nom: "Cannelle, muscade, zeste citron vert, vanille", qte: "PM", cout: 1.2 },
    ],
    etapes: [
      "Mélanger laits, sucre et épices, laisser infuser 1h au froid",
      "Chinoiser finement l'appareil",
      "Turbiner en sorbetière (à la sorbetière manuelle 'au bâton' pour la tradition)",
      "Stocker à -18°C, tempérer 5 min avant service en boules",
    ],
    allergenes: ["Lait"],
    dressage: [{ element: "Boules de sorbet", qte: "3 pièces (≈ 120 g)" }, { element: "Coco râpé torréfié", qte: "8 g" }, { element: "Tuile coco", qte: "1 pièce" }, { element: "Feuille de menthe", qte: "1 pièce" }],
  },
];

const RELEVES_INIT = [
  { id: 1, equipement: "Chambre froide positive", cible: "0 à +4°C", valeur: 3.2, heure: "07:15", statut: "ok" },
  { id: 2, equipement: "Chambre froide négative", cible: "-18°C max", valeur: -19.5, heure: "07:18", statut: "ok" },
  { id: 3, equipement: "Vitrine réfrigérée", cible: "0 à +4°C", valeur: 5.8, heure: "07:20", statut: "alerte" },
  { id: 4, equipement: "Bain-marie service", cible: "+63°C min", valeur: 68.0, heure: "11:45", statut: "ok" },
];

const FOURNISSEURS = [
  { id: 1, nom: "Madivial", type: "Viandes & volailles", tel: "0596 51 22 80", delai: "J+1", note: 4.5, commandes: 12 },
  { id: 2, nom: "Marée des Îles", type: "Poissons & fruits de mer", tel: "0596 63 41 17", delai: "J+1 (lun-ven)", note: 4.8, commandes: 8 },
  { id: 3, nom: "Sodex Antilles", type: "Épicerie, sec & consommables", tel: "0596 50 38 92", delai: "J+2", note: 4.2, commandes: 15 },
  { id: 4, nom: "Ti' Marché Local", type: "Fruits & légumes pays", tel: "0696 28 74 50", delai: "J+1", note: 4.9, commandes: 21 },
  { id: 5, nom: "Boissons Caraïbes", type: "Bar, rhums & softs", tel: "0596 71 05 33", delai: "J+2 (mar/jeu)", note: 4.4, commandes: 9 },
];

// ---- PRÉ-COMMANDES : produits à réapprovisionner (sous seuil ou proche) ----
// Quantité suggérée = recomplètement à 2× le seuil
function produitsACommander(stocks) {
  return stocks
    .filter(s => s.qte <= s.seuil * 1.2)
    .map(s => ({ ...s, suggestion: Math.max(1, Math.ceil(s.seuil * 2 - s.qte)) }));
}

// ============ COMPOSANTS ============

function Badge({ children, color = C.or, bg }) {
  return (
    <span style={{
      display: "inline-block", padding: "2px 10px", borderRadius: 12,
      fontSize: 11, fontWeight: 700, letterSpacing: 0.5,
      color: bg ? C.blanc : color, background: bg || "transparent",
      border: bg ? "none" : `1.5px solid ${color}`,
      fontFamily: "Inter, system-ui, sans-serif", textTransform: "uppercase",
    }}>{children}</span>
  );
}

function Carte({ children, style = {} }) {
  return (
    <div style={{
      background: C.blanc, border: "1px solid rgba(200,150,42,0.28)",
      borderRadius: 16, padding: 18,
      boxShadow: "0 2px 12px rgba(53,39,24,0.06), 0 1px 3px rgba(53,39,24,0.05)", ...style,
    }}>{children}</div>
  );
}

function Stat({ label, valeur, sous, accent }) {
  return (
    <Carte style={{ flex: 1, minWidth: 130, borderTop: `3px solid ${accent || C.or}` }}>
      <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: 1, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>{label}</div>
      <div style={{ fontSize: 26, fontWeight: 700, color: C.brun, fontFamily: "Fraunces, Georgia, serif", marginTop: 4 }}>{valeur}</div>
      {sous && <div style={{ fontSize: 12, color: accent || C.brunMoyen, marginTop: 2, fontFamily: "Inter, system-ui, sans-serif" }}>{sous}</div>}
    </Carte>
  );
}

// ============ ÉCRANS ============

function SyntheseMois({ rapportsZ }) {
  const histo = rapportsZ;
  if (!histo.length) return null;
  const caMois = histo.reduce((s, z) => s + z.caTTC, 0);
  const couvertsMois = histo.reduce((s, z) => s + z.couverts, 0);
  const ticketMoyen = couvertsMois ? caMois / couvertsMois : 0;
  const meilleur = histo.reduce((a, b) => a.caTTC > b.caTTC ? a : b);
  const pire = histo.reduce((a, b) => a.caTTC < b.caTTC ? a : b);
  const partCB = histo.reduce((s, z) => s + (z.modes["CB"] || 0), 0) / caMois * 100;
  const evol = MOIS_PRECEDENT.caTTC ? (caMois - MOIS_PRECEDENT.caTTC) / MOIS_PRECEDENT.caTTC * 100 : 0;
  // CA par semaine (par tranche du mois)
  const semaines = [[], [], [], [], []];
  for (const z of histo) {
    const j = parseInt(z.date.slice(0, 2), 10);
    semaines[Math.min(4, Math.floor((j - 1) / 7))].push(z);
  }
  const caSemaines = semaines.map(s => s.reduce((t, z) => t + z.caTTC, 0)).filter((_, i) => semaines[i].length > 0 || i < 4);
  const maxSem = Math.max(...caSemaines, 1);

  return (
    <Carte style={{ marginBottom: 16, borderTop: `4px solid ${C.brun}` }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 6 }}>
        <h3 style={{ ...h3Style, margin: 0 }}>Synthèse du mois · Mai</h3>
        <span style={{
          fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, padding: "4px 12px", borderRadius: 12,
          background: evol >= 0 ? "#E3EBDD" : "#F3DCD4", color: evol >= 0 ? C.vert : C.rouille,
          border: `1.5px solid ${evol >= 0 ? C.vert : C.rouille}`,
        }}>{evol >= 0 ? "▲" : "▼"} {Math.abs(evol).toFixed(1)}% vs {MOIS_PRECEDENT.libelle}</span>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))", gap: 10, marginTop: 12 }}>
        {[
          ["CA du mois", `${(caMois / 1000).toFixed(1)}k €`, `${histo.length} services`],
          ["Couverts", String(couvertsMois), `≈ ${Math.round(couvertsMois / histo.length)}/service`],
          ["Ticket moyen", `${ticketMoyen.toFixed(2)} €`, "Par couvert"],
          ["Part CB", `${partCB.toFixed(0)}%`, "Du CA total"],
        ].map(([l, v, s]) => (
          <div key={l} style={{ padding: "10px 12px", background: C.creme, borderRadius: 10, border: `1px solid ${C.orClair}` }}>
            <div style={{ fontSize: 10, textTransform: "uppercase", letterSpacing: 1, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>{l}</div>
            <div style={{ fontSize: 21, fontWeight: 700, color: C.brun, fontFamily: "Fraunces, Georgia, serif", marginTop: 2 }}>{v}</div>
            <div style={{ fontSize: 10.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>{s}</div>
          </div>
        ))}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1.1fr 1fr", gap: 14, marginTop: 14 }}>
        <div>
          <div style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: 1, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginBottom: 8 }}>CA par semaine</div>
          <div style={{ display: "flex", alignItems: "flex-end", gap: 10, height: 90 }}>
            {caSemaines.map((ca, i) => (
              <div key={i} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 3 }}>
                <span style={{ fontSize: 9.5, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, color: C.brun }}>{(ca / 1000).toFixed(1)}k</span>
                <div style={{ width: "100%", maxWidth: 52, borderRadius: "5px 5px 0 0", height: `${Math.max(6, ca / maxSem * 60)}px`, background: ca === maxSem ? C.or : C.brunMoyen }} />
                <span style={{ fontSize: 9.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>S{i + 1}</span>
              </div>
            ))}
          </div>
        </div>
        <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, color: C.brun, lineHeight: 1.9 }}>
          <div>🏆 Meilleur service : <b>{meilleur.date}</b> · <b style={{ color: C.or }}>{meilleur.caTTC.toFixed(0)} €</b> ({meilleur.couverts} cv)</div>
          <div>📉 Service le plus calme : <b>{pire.date}</b> · {pire.caTTC.toFixed(0)} € ({pire.couverts} cv)</div>
          <div>💶 CA moyen / service : <b>{(caMois / histo.length).toFixed(0)} €</b></div>
        </div>
      </div>
    </Carte>
  );
}

function Dashboard({ goTo, releves, mep, stocks, rapportsZ, reservations, ventes }) {
  const alertes = releves.filter(r => r.statut === "alerte").length;
  const foodCostMoyen = (FICHES.reduce((s, f) => s + f.coutPortion / f.prixVente, 0) / FICHES.length * 100).toFixed(1);
  return (
    <div>
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 20 }}>
        <Stat label="Fiches actives" valeur={FICHES.length} sous="Carte du jour" />
        <Stat label="Food cost moyen" valeur={`${foodCostMoyen}%`} sous="Objectif < 30%" accent={foodCostMoyen < 30 ? C.vert : C.rouille} />
        <Stat label="Alertes HACCP" valeur={alertes} sous={alertes > 0 ? "Action requise" : "Tout est conforme"} accent={alertes > 0 ? C.rouille : C.vert} />
        <Stat label="Fournisseurs" valeur={FOURNISSEURS.length} sous="56 commandes ce mois" />
      </div>

      <SyntheseMois rapportsZ={rapportsZ} />
      {releves.filter(r => r.statut === "alerte").map(r => (
        <Carte key={r.id} style={{ marginBottom: 16, background: C.cremeFonce, borderLeft: `4px solid ${C.rouille}` }}>
          <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.rouille, marginBottom: 6 }}>⚠ {r.equipement} : {r.valeur}°C relevé à {r.heure}</div>
          <div style={{ fontSize: 13, color: C.brun, fontFamily: "Inter, system-ui, sans-serif", lineHeight: 1.5 }}>
            Température hors plage (cible {r.cible}). Action corrective : vérifier l'équipement, déplacer les denrées sensibles, recontrôler dans 1h et tracer l'incident au PMS.
          </div>
          <button onClick={() => goTo("haccp")} style={btnStyle(C.rouille)}>Voir le registre HACCP →</button>
        </Carte>
      ))}

      {/* ===== TOPS & FLOPS DU MOIS PAR CATÉGORIE ===== */}
      <Carte style={{ marginBottom: 16 }}>
        <h3 style={h3Style}>Tops & flops du mois — ce qui se vend (et ce qui dort)</h3>
        {(() => {
          // Cumul : stats du mois + ventes du jour en direct
          const compteur = { ...STATS_PLATS_INIT };
          for (const v of ventes) for (const i of v.items) compteur[i.ficheId] = (compteur[i.ficheId] || 0) + i.qte;
          return ["Entrée", "Plat", "Dessert"].map(cat => {
            const liste = FICHES.filter(f => f.categorie === cat)
              .map(f => ({ ...f, vendus: compteur[f.id] || 0 }))
              .sort((a, b) => b.vendus - a.vendus);
            if (liste.length < 2) return null;
            const top = liste[0], flop = liste[liste.length - 1];
            const maxV = Math.max(top.vendus, 1);
            const ligne = (p, type) => (
              <div style={{ flex: 1, minWidth: 230, padding: "10px 12px", borderRadius: 12, background: type === "top" ? "#EDF3E8" : "#F8EFE0", border: `1.5px solid ${type === "top" ? C.vert : C.or}` }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
                  <span style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 12.5, fontWeight: 700, color: C.brun }}>
                    {type === "top" ? "🏆" : "📉"} {p.nom}
                  </span>
                  <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 15, color: type === "top" ? C.vert : C.rouille }}>{p.vendus}</span>
                </div>
                <div style={{ height: 6, background: "rgba(53,39,24,0.08)", borderRadius: 3, margin: "6px 0 4px" }}>
                  <div style={{ height: 6, width: `${Math.max(4, p.vendus / maxV * 100)}%`, background: type === "top" ? C.vert : C.or, borderRadius: 3 }} />
                </div>
                <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>
                  {(p.vendus * p.prixVente).toFixed(0)} € de CA · marge {(p.prixVente - p.coutPortion).toFixed(2)} €/portion
                  {type === "flop" && p.vendus < top.vendus / 3 && " · à retravailler ou sortir de carte ?"}
                </div>
              </div>
            );
            return (
              <div key={cat} style={{ marginBottom: 12 }}>
                <div style={{ fontSize: 10.5, letterSpacing: 1.5, textTransform: "uppercase", color: C.or, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, marginBottom: 6 }}>{cat}s</div>
                <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
                  {ligne(top, "top")}
                  {ligne(flop, "flop")}
                </div>
              </div>
            );
          });
        })()}
        <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginTop: 4 }}>
          Quantités vendues sur le mois (les ventes du jour s'ajoutent en direct). Demande au pilote IA une analyse : « pourquoi mon sorbet coco ne part pas ? »
        </div>
      </Carte>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <Carte>
          <h3 style={h3Style}>Marges par plat</h3>
          {FICHES.map(f => {
            const fc = (f.coutPortion / f.prixVente * 100);
            return (
              <div key={f.id} style={{ marginBottom: 10 }}>
                <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, marginBottom: 3 }}>
                  <span>{f.nom}</span><span style={{ fontWeight: 700 }}>{fc.toFixed(0)}%</span>
                </div>
                <div style={{ height: 8, background: C.cremeFonce, borderRadius: 4 }}>
                  <div style={{ height: 8, width: `${fc}%`, background: fc < 30 ? C.vert : C.rouille, borderRadius: 4 }} />
                </div>
              </div>
            );
          })}
          <div style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 8 }}>Food cost = coût matière / prix de vente HT</div>
        </Carte>
        <Carte>
          <h3 style={h3Style}>Actions du jour</h3>
          {(() => {
            const actions = [];
            const dlcCrit = FICHES.filter(f => mep[f.id]?.valide && mep[f.id].dispo > 0 && Math.ceil(((mep[f.id].prodLe || Date.now()) + 3 * 86400000 - Date.now()) / 86400000) <= 1);
            if (dlcCrit.length) actions.push({ t: `DLC critique : ${dlcCrit.map(f => `${f.nom} (${mep[f.id].dispo} portions)`).join(", ")} — écouler ou jeter`, c: C.rouille, go: "haccp" });
            const alertesT = releves.filter(r => r.statut === "alerte");
            if (alertesT.length) actions.push({ t: `Corriger ${alertesT.map(r => r.equipement).join(", ")} (T° hors plage) et recontrôler`, c: C.rouille, go: "haccp" });
            const ruptures = stocks.filter(s => s.qte <= s.seuil);
            if (ruptures.length) actions.push({ t: `Commander : ${ruptures.slice(0, 3).map(r => r.produit).join(", ")}${ruptures.length > 3 ? ` +${ruptures.length - 3}` : ""}`, c: C.rouille, go: "fournisseurs" });
            const nonValides = FICHES.length - Object.values(mep).filter(m => m.valide).length;
            if (nonValides > 0) actions.push({ t: `Valider la mise en place (${nonValides} plat(s) restant(s))`, c: C.or, go: "cuisine" });
            const faibles = FICHES.filter(f => mep[f.id]?.valide && mep[f.id].dispo > 0 && mep[f.id].dispo <= 3);
            if (faibles.length) actions.push({ t: `Bientôt épuisé en salle : ${faibles.map(f => f.nom).join(", ")}`, c: C.or, go: null });
            const resasSansTable = reservations.filter(r => r.statut === "a_venir" && !r.table);
            if (resasSansTable.length) actions.push({ t: `Attribuer une table : ${resasSansTable.map(r => `${r.nom} (${r.heure})`).join(", ")}`, c: C.brunMoyen, go: "resa" });
            actions.push({ t: "Relevé T° de mi-journée (12h00) puis fin de service", c: C.brunMoyen, go: "haccp" });
            actions.push({ t: "Clôture : photographier le Z de la caisse en fin de service", c: C.brunMoyen, go: "zanalyse" });
            return actions.map((a, i) => (
              <div key={i} onClick={() => a.go && goTo(a.go)} style={{ display: "flex", gap: 8, padding: "8px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 13, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, cursor: a.go ? "pointer" : "default" }}>
                <span style={{ color: a.c }}>◆</span><span style={{ flex: 1 }}>{a.t}</span>{a.go && <span style={{ color: C.brunMoyen }}>→</span>}
              </div>
            ));
          })()}
        </Carte>
      </div>
    </div>
  );
}

function FichesTechniques({ supprimerFiche, mep, setMep, stocks, setStocks, goTo, majFiches }) {
  const photoRef = useRef(null);
  // Compression de la photo (≈ 640px, JPEG) pour rester léger en sauvegarde
  const prendrePhoto = (file, fiche) => {
    if (!file) return;
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      const max = 640;
      const ratio = Math.min(1, max / Math.max(img.width, img.height));
      const cv = document.createElement("canvas");
      cv.width = Math.round(img.width * ratio);
      cv.height = Math.round(img.height * ratio);
      cv.getContext("2d").drawImage(img, 0, 0, cv.width, cv.height);
      fiche.photo = cv.toDataURL("image/jpeg", 0.72);
      URL.revokeObjectURL(url);
      majFiches();
    };
    img.src = url;
  };
  // Valider la mise en place depuis la fiche, avec la quantité de portions choisie
  const validerDepuisFiche = (fiche, portions) => {
    const deductions = calculerDeductions(stocks, fiche, portions);
    setStocks(stocks.map(s => {
      const d = deductions.find(x => x.stockId === s.id);
      return d ? { ...s, qte: Math.max(0, +(s.qte - d.qte).toFixed(2)) } : s;
    }));
    setMep({ ...mep, [fiche.id]: { valide: true, dispo: portions, deductions, prodLe: Date.now() } });
  };
  const annulerDepuisFiche = (fiche) => {
    const m = mep[fiche.id];
    if (!m) return;
    setStocks(stocks.map(s => {
      const d = (m.deductions || []).find(x => x.stockId === s.id);
      return d ? { ...s, qte: +(s.qte + d.qte).toFixed(2) } : s;
    }));
    const nv = { ...mep };
    delete nv[fiche.id];
    setMep(nv);
  };
  // Puce de rapprochement avec la mise en place du module Cuisine
  const puceMep = (f, taille = "normal") => {
    const m = mep[f.id];
    const petit = taille === "petit";
    const style = {
      display: "inline-flex", alignItems: "center", gap: 5,
      fontSize: petit ? 10.5 : 11.5, fontWeight: 600, fontFamily: "Inter, system-ui, sans-serif",
      padding: petit ? "3px 9px" : "4px 11px", borderRadius: 12,
    };
    if (!m?.valide) return (
      <span style={{ ...style, background: C.cremeFonce, color: C.brunMoyen, border: `1.5px dashed ${C.orClair}` }}>
        ○ {petit ? "MEP à faire" : "Fiche réalisée ? Valider la MEP"}
      </span>
    );
    if (m.dispo === 0) return (
      <span style={{ ...style, background: "#F6E0D7", color: C.rouille, border: `1.5px solid ${C.rouille}` }}>
        ● Épuisé (86)
      </span>
    );
    return (
      <span style={{ ...style, background: m.dispo <= 3 ? "#F6ECD4" : "#E7EFE2", color: m.dispo <= 3 ? C.or : C.vert, border: `1.5px solid ${m.dispo <= 3 ? C.or : C.vert}` }}>
        ● MEP ✓ · {m.dispo} dispo
      </span>
    );
  };
  const [sel, setSel] = useState(null);
  const [portions, setPortions] = useState(null);
  const [filtre, setFiltre] = useState("Tout");
  const [confirmId, setConfirmId] = useState(null);
  const [recherche, setRecherche] = useState("");
  if (sel) {
    const f = sel;
    const ratio = (portions || f.portions) / f.portions;
    const fc = (f.coutPortion / f.prixVente * 100).toFixed(1);
    return (
      <div>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <button onClick={() => { setSel(null); setPortions(null); setConfirmId(null); }} style={btnStyle(C.brunMoyen)}>← Retour à la carte</button>
          <button onClick={() => {
            if (confirmId === f.id) { supprimerFiche(f.id); setSel(null); setPortions(null); setConfirmId(null); }
            else setConfirmId(f.id);
          }} style={btnStyle(confirmId === f.id ? C.rouille : C.brunMoyen)}>
            {confirmId === f.id ? "⚠ Confirmer la suppression" : "🗑 Supprimer cette fiche"}
          </button>
        </div>
        <Carte style={{ marginTop: 12, borderTop: `4px solid ${C.or}` }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", flexWrap: "wrap", gap: 8 }}>
            <div>
              <h2 style={{ fontFamily: "Fraunces, Georgia, serif", color: C.brun, margin: 0, fontSize: 24 }}>{f.nom}</h2>
              <div style={{ marginTop: 8, display: "flex", gap: 6, flexWrap: "wrap" }}>
                <Badge bg={C.or}>{f.categorie}</Badge>
                <Badge>{f.tempsPrepa} prépa</Badge>
                <Badge>{f.tempsCuisson} cuisson</Badge>
                <Badge color={parseFloat(fc) < 30 ? C.vert : C.rouille}>Food cost {fc}%</Badge>
                <span
                  onClick={() => mep[f.id]?.valide ? annulerDepuisFiche(f) : validerDepuisFiche(f, portions || f.portions)}
                  style={{ cursor: "pointer" }}
                  title={mep[f.id]?.valide ? "Annuler la mise en place (restitue les matières)" : `Fiche réalisée → valider la MEP pour ${portions || f.portions} portions (déduit l'inventaire)`}
                >{puceMep(f)}</span>
              </div>
              {!mep[f.id]?.valide && (
                <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginTop: 6, fontStyle: "italic" }}>
                  Ajuste les portions avec − / + puis touche la puce ○ pour valider la mise en place ({portions || f.portions} portions seront disponibles à la vente).
                </div>
              )}
            </div>
            <div style={{ textAlign: "right", fontFamily: "Inter, system-ui, sans-serif" }}>
              <div style={{ fontSize: 11, color: C.brunMoyen, textTransform: "uppercase" }}>Portions</div>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 4 }}>
                <button onClick={() => setPortions(Math.max(1, (portions || f.portions) - f.portions))} style={miniBtn}>−</button>
                <span style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 22, fontWeight: 700, color: C.brun, minWidth: 36, textAlign: "center" }}>{portions || f.portions}</span>
                <button onClick={() => setPortions((portions || f.portions) + f.portions)} style={miniBtn}>+</button>
              </div>
            </div>
          </div>

          <div style={{ display: "grid", gridTemplateColumns: "1fr 1.2fr", gap: 16, marginTop: 18 }}>
            <div>
              <h3 style={h3Style}>Ingrédients {ratio !== 1 && <span style={{ color: C.rouille, fontSize: 12 }}>(×{ratio})</span>}</h3>
              {f.ingredients.map((ing, i) => (
                <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "6px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 13, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                  <span>{ing.nom} <span style={{ color: ratio !== 1 ? C.rouille : C.brunMoyen, fontWeight: ratio !== 1 ? 700 : 400 }}>· {adapterQte(ing.qte, ratio)}</span></span>
                  <span style={{ fontWeight: 700 }}>{(ing.cout * ratio).toFixed(2)} €</span>
                </div>
              ))}
              <div style={{ display: "flex", justifyContent: "space-between", padding: "10px 0", fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun, fontSize: 15 }}>
                <span>Coût matière total</span>
                <span style={{ color: C.or }}>{(f.ingredients.reduce((s, x) => s + x.cout, 0) * ratio).toFixed(2)} €</span>
              </div>
              <div style={{ marginTop: 8, padding: 10, background: C.cremeFonce, borderRadius: 8, fontSize: 12, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                <b>Allergènes :</b> {f.allergenes.join(", ")}
              </div>
            </div>
            <div>
              <h3 style={h3Style}>Processus</h3>
              {f.etapes.map((e, i) => (
                <div key={i} style={{ display: "flex", gap: 10, marginBottom: 10 }}>
                  <div style={{ width: 24, height: 24, borderRadius: "50%", background: C.or, color: C.blanc, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12, fontWeight: 700, fontFamily: "Inter, system-ui, sans-serif", flexShrink: 0 }}>{i + 1}</div>
                  <div style={{ fontSize: 13, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, lineHeight: 1.5 }}>{e}</div>
                </div>
              ))}
              {f.dressage && f.dressage.length > 0 && (
                <div style={{ marginTop: 14 }}>
                  <h3 style={h3Style}>Dressage de l'assiette <span style={{ fontSize: 11, color: C.brunMoyen, fontWeight: 400 }}>(par portion)</span></h3>
                  <input ref={photoRef} type="file" accept="image/*" capture="environment" style={{ display: "none" }}
                    onChange={e => { prendrePhoto(e.target.files?.[0], f); e.target.value = ""; }} />
                  {f.photo ? (
                    <div style={{ position: "relative", marginBottom: 10 }}>
                      <img src={f.photo} alt={`Dressage ${f.nom}`} style={{ width: "100%", maxHeight: 240, objectFit: "cover", borderRadius: 14, border: `2px solid ${C.or}`, display: "block", boxShadow: "0 4px 14px rgba(53,39,24,0.18)" }} />
                      <span style={{ position: "absolute", bottom: 8, left: 8, background: "rgba(53,39,24,0.78)", color: C.creme, fontSize: 10, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 600, padding: "3px 10px", borderRadius: 10 }}>📷 Assiette témoin</span>
                      <button onClick={() => photoRef.current?.click()} style={{ position: "absolute", top: 8, right: 8, background: "rgba(255,254,250,0.92)", color: C.brun, border: `1.5px solid ${C.or}`, borderRadius: 10, padding: "4px 10px", fontSize: 10.5, fontWeight: 700, cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif" }}>Remplacer</button>
                    </div>
                  ) : (
                    <button onClick={() => photoRef.current?.click()} style={{
                      width: "100%", marginBottom: 10, padding: "22px 12px", borderRadius: 14,
                      border: `2px dashed ${C.orClair}`, background: "#FBF7EC", cursor: "pointer",
                      fontFamily: "Inter, system-ui, sans-serif", fontSize: 12.5, color: C.brunMoyen, fontWeight: 600,
                    }}>
                      📷 Photographier l'assiette témoin
                      <div style={{ fontSize: 10.5, fontWeight: 400, marginTop: 4, fontStyle: "italic" }}>La référence visuelle du dressage, pour une assiette identique à chaque service</div>
                    </button>
                  )}
                  <div style={{ padding: "10px 12px", background: "#FBF7EC", border: `1.5px dashed ${C.or}`, borderRadius: 12 }}>
                    {f.dressage.map((d, i) => (
                      <div key={i} style={{ display: "flex", alignItems: "baseline", gap: 8, padding: "4px 0", fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                        <span style={{ color: C.or, fontSize: 10 }}>◆</span>
                        <span style={{ flex: 1 }}>{d.element}</span>
                        <span style={{ fontWeight: 700, color: C.brunMoyen, whiteSpace: "nowrap" }}>{d.qte}</span>
                      </div>
                    ))}
                  </div>
                </div>
              )}
              <div style={{ marginTop: 12, padding: 12, background: C.creme, border: `1px solid ${C.orClair}`, borderRadius: 8, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, color: C.brun }}>
                <div style={{ display: "flex", justifyContent: "space-between" }}><span>Coût / portion</span><b>{f.coutPortion.toFixed(2)} €</b></div>
                <div style={{ display: "flex", justifyContent: "space-between" }}><span>Prix de vente</span><b>{f.prixVente.toFixed(2)} €</b></div>
                <div style={{ display: "flex", justifyContent: "space-between", color: C.vert }}><span>Marge brute / portion</span><b>{(f.prixVente - f.coutPortion).toFixed(2)} €</b></div>
              </div>
            </div>
          </div>
        </Carte>
      </div>
    );
  }
  const cats = ["Tout", "Entrée", "Plat", "Accompagnement", "Dessert"];
  const visibles = (filtre === "Tout" ? FICHES : FICHES.filter(f => f.categorie === filtre))
    .filter(f => !recherche.trim() || (f.nom + " " + (f.description || "") + " " + f.ingredients.map(i => i.nom).join(" ")).toLowerCase().includes(recherche.toLowerCase()));
  return (
    <div>
    <input value={recherche} onChange={e => setRecherche(e.target.value)} placeholder="🔍 Rechercher un plat, un ingrédient…"
      style={{ width: "100%", padding: "11px 14px", border: "1.5px solid rgba(200,150,42,0.4)", borderRadius: 12, fontFamily: "Inter, system-ui, sans-serif", fontSize: 14, background: C.blanc, color: C.brun, marginBottom: 12, boxSizing: "border-box" }} />
    <div style={{ display: "flex", gap: 6, marginBottom: 14, flexWrap: "wrap" }}>
      {cats.map(c => (
        <button key={c} onClick={() => setFiltre(c)} style={{
          background: filtre === c ? C.brun : C.blanc,
          color: filtre === c ? C.creme : C.brun,
          border: `1.5px solid ${filtre === c ? C.brun : C.orClair}`,
          borderRadius: 16, padding: "6px 14px", fontSize: 12, fontWeight: 700,
          cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
        }}>{c} {c !== "Tout" && `(${FICHES.filter(f => f.categorie === c).length})`}</button>
      ))}
    </div>
    <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(230px, 1fr))", gap: 12 }}>
      {visibles.map(f => {
        const fc = (f.coutPortion / f.prixVente * 100);
        return (
          <Carte key={f.id} style={{ cursor: "pointer", borderTop: `3px solid ${C.or}`, position: "relative" }}>
            <button onClick={(e) => {
              e.stopPropagation();
              if (confirmId === f.id) { supprimerFiche(f.id); setConfirmId(null); }
              else setConfirmId(f.id);
            }} title="Supprimer la fiche" style={{
              position: "absolute", top: 8, right: 8, zIndex: 2,
              background: confirmId === f.id ? C.rouille : "transparent",
              color: confirmId === f.id ? C.blanc : C.brunMoyen,
              border: `1.5px solid ${confirmId === f.id ? C.rouille : C.orClair}`,
              borderRadius: confirmId === f.id ? 12 : "50%",
              width: confirmId === f.id ? "auto" : 24, height: 24,
              padding: confirmId === f.id ? "0 10px" : 0,
              fontSize: confirmId === f.id ? 10.5 : 12, fontWeight: 700,
              cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif", lineHeight: 1,
            }}>{confirmId === f.id ? "Confirmer ?" : "🗑"}</button>
            <div onClick={() => { setSel(f); setConfirmId(null); }}>
              {f.photo && <img src={f.photo} alt="" style={{ width: "calc(100% + 36px)", margin: "-18px -18px 10px", height: 110, objectFit: "cover", borderRadius: "14px 14px 0 0", display: "block" }} />}
              <Badge bg={C.brunMoyen}>{f.categorie}</Badge>
              <h3 style={{ fontFamily: "Fraunces, Georgia, serif", color: C.brun, margin: "10px 0 6px", fontSize: 18 }}>{f.nom}</h3>
              <div style={{ fontSize: 12, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>{f.portions} portions · {f.tempsPrepa} + {f.tempsCuisson}</div>
              <div style={{ marginTop: 10, display: "flex", justifyContent: "space-between", fontFamily: "Inter, system-ui, sans-serif", fontSize: 13 }}>
                <span style={{ color: fc < 30 ? C.vert : C.rouille, fontWeight: 700 }}>FC {fc.toFixed(0)}%</span>
                <span style={{ color: C.or, fontWeight: 700 }}>{f.prixVente.toFixed(2)} €</span>
              </div>
              <div style={{ marginTop: 8 }}>{puceMep(f, "petit")}</div>
            </div>
          </Carte>
        );
      })}
    </div>
    </div>
  );
}

const DLC_JOURS = 3; // préparations maison : J+3 (règle usuelle PMS)
function dlcInfos(m) {
  const prod = m.prodLe || Date.now();
  const dlc = prod + DLC_JOURS * 86400000;
  const joursRestants = Math.ceil((dlc - Date.now()) / 86400000);
  return { prod, dlc, joursRestants };
}

function Haccp({ releves, setReleves, mep, setMep }) {
  const productions = FICHES.filter(f => mep[f.id]?.valide && mep[f.id].dispo > 0)
    .map(f => ({ fiche: f, m: mep[f.id], ...dlcInfos(mep[f.id]) }))
    .sort((a, b) => a.joursRestants - b.joursRestants);
  const dlcCritiques = productions.filter(p => p.joursRestants <= 1);

  const jeterProduction = (ficheId) => {
    setMep(prev => { const nv = { ...prev }; delete nv[ficheId]; return nv; });
  };
  const fmtDate = (ts) => new Date(ts).toLocaleDateString("fr-FR", { weekday: "short", day: "2-digit", month: "2-digit" });

  const [valeurs, setValeurs] = useState({});
  const ajouter = (id) => {
    const v = parseFloat(String(valeurs[id]).replace(",", "."));
    if (isNaN(v)) return;
    setReleves(releves.map(r => {
      if (r.id !== id) return r;
      const ok = r.cible.includes("min") ? v >= parseFloat(r.cible) || v >= 63
        : r.cible.includes("-18") ? v <= -18
        : v >= 0 && v <= 4;
      return { ...r, valeur: v, heure: new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }), statut: ok ? "ok" : "alerte" };
    }));
    setValeurs({ ...valeurs, [id]: "" });
  };
  return (
    <div>
      <Carte style={{ marginBottom: 14, background: C.cremeFonce }}>
        <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun }}>Registre des températures — {new Date().toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long" })}</div>
        <div style={{ fontSize: 12, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 4 }}>Traçabilité conforme au Plan de Maîtrise Sanitaire. Saisis un relevé : Mizen Pro vérifie automatiquement la conformité et archive.</div>
      </Carte>
      {/* ===== DLC DES MISES EN PLACE AU FRIGO ===== */}
      <Carte style={{ marginBottom: 14, borderTop: `4px solid ${dlcCritiques.length ? C.rouille : C.vert}` }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 6 }}>
          <h3 style={{ ...h3Style, margin: 0 }}>DLC des mises en place au frigo</h3>
          <Badge bg={dlcCritiques.length ? C.rouille : C.vert}>{dlcCritiques.length ? `⚠ ${dlcCritiques.length} critique(s)` : "✓ Tout est dans les temps"}</Badge>
        </div>
        <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, margin: "6px 0 10px" }}>
          Préparations maison : DLC à J+{DLC_JOURS} après production (PMS). Les portions restantes proviennent de la mise en place — écouler en priorité, jeter et tracer si dépassé.
        </div>
        {!productions.length && <div style={{ fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontStyle: "italic" }}>Aucune production au frigo — valide une mise en place pour démarrer le suivi.</div>}
        {productions.map(p => {
          const depassee = p.joursRestants <= 0;
          const critique = p.joursRestants === 1;
          const tropRestant = !depassee && critique && p.m.dispo >= 4;
          return (
            <div key={p.fiche.id} style={{
              display: "flex", alignItems: "center", gap: 10, padding: "9px 10px", marginBottom: 6, borderRadius: 10, flexWrap: "wrap",
              background: depassee ? "#F6E0D7" : critique ? "#F8F0DC" : "#F0F4EC",
              border: `1.5px solid ${depassee ? C.rouille : critique ? C.or : C.vert}`,
            }}>
              <div style={{ flex: 1, minWidth: 170 }}>
                <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, fontWeight: 700, color: C.brun }}>{p.fiche.nom}</div>
                <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>
                  Produit le {fmtDate(p.prod)} · DLC {fmtDate(p.dlc)}
                </div>
              </div>
              <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 16, color: depassee ? C.rouille : critique ? C.or : C.vert }}>
                {p.m.dispo} portion(s)
              </span>
              <Badge bg={depassee ? C.rouille : critique ? C.or : C.vert}>
                {depassee ? "DLC DÉPASSÉE — jeter" : critique ? "J-1 · à écouler aujourd'hui" : `J-${p.joursRestants}`}
              </Badge>
              {tropRestant && <Badge bg={C.rouille}>⚠ {p.m.dispo} portions restantes du {fmtDate(p.prod)} !</Badge>}
              {tropRestant && <span style={{ width: "100%", fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, fontStyle: "italic" }}>Suggestion : plat du jour, formule midi ou -20% pour écouler avant ce soir.</span>}
              {depassee && (
                <button onClick={() => jeterProduction(p.fiche.id)} style={{ ...btnStyle(C.rouille), marginTop: 0 }}>🗑 Jeter & tracer</button>
              )}
            </div>
          );
        })}
      </Carte>

      {releves.map(r => (
        <Carte key={r.id} style={{ marginBottom: 10, borderLeft: `4px solid ${r.statut === "ok" ? C.vert : C.rouille}` }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 10 }}>
            <div>
              <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun }}>{r.equipement}</div>
              <div style={{ fontSize: 12, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>Cible : {r.cible} · Dernier relevé : <b>{r.valeur}°C</b> à {r.heure}</div>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <Badge bg={r.statut === "ok" ? C.vert : C.rouille}>{r.statut === "ok" ? "Conforme" : "Alerte"}</Badge>
              <input
                value={valeurs[r.id] || ""}
                onChange={e => setValeurs({ ...valeurs, [r.id]: e.target.value })}
                placeholder="°C"
                style={{ width: 64, padding: "7px 8px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, background: C.blanc, color: C.brun }}
              />
              <button onClick={() => ajouter(r.id)} style={btnStyle(C.or)}>Relever</button>
            </div>
          </div>
        </Carte>
      ))}
    </div>
  );
}

// ---- CATALOGUES FOURNISSEURS (synchronisés via leurs portails/API — démo) ----
const CATALOGUES = {
  1: [ // Madivial
    { ref: "VOL-012", produit: "Cuisses de poulet (kg)", prix: 4.50, promo: null },
    { ref: "VOL-031", produit: "Poulet fermier entier (kg)", prix: 6.20, promo: -10 },
    { ref: "POR-114", produit: "Épaule de porc (kg)", prix: 5.90, promo: null },
    { ref: "POR-118", produit: "Lard fumé pays (kg)", prix: 8.80, promo: -15 },
    { ref: "BOE-201", produit: "Bœuf à bourguignon (kg)", prix: 11.50, promo: null },
  ],
  2: [ // Marée des Îles
    { ref: "POI-008", produit: "Vivaneau entier (kg)", prix: 11.00, promo: null },
    { ref: "POI-015", produit: "Morue salée (kg)", prix: 9.00, promo: -12 },
    { ref: "CRU-022", produit: "Ouassous calibre 20/30 (kg)", prix: 14.00, promo: null },
    { ref: "CRU-040", produit: "Crabes de terre (pièce)", prix: 2.40, promo: -20 },
    { ref: "MOL-051", produit: "Chatrou nettoyé (kg)", prix: 10.00, promo: null },
  ],
  3: [ // Sodex Antilles
    { ref: "EPI-303", produit: "Riz long grain 25kg (sac)", prix: 38.00, promo: -8 },
    { ref: "EPI-310", produit: "Farine T55 25kg (sac)", prix: 27.50, promo: null },
    { ref: "EPI-355", produit: "Lait de coco 1L (carton ×12)", prix: 58.00, promo: null },
    { ref: "CON-401", produit: "Serviettes papier (carton 3000)", prix: 79.00, promo: -10 },
    { ref: "HYG-422", produit: "Produit vitres 5L (bidon)", prix: 21.50, promo: null },
  ],
  4: [ // Ti' Marché Local
    { ref: "LEG-101", produit: "Christophines (kg)", prix: 2.40, promo: null },
    { ref: "LEG-108", produit: "Ignames pays (kg)", prix: 2.75, promo: -15 },
    { ref: "FRU-130", produit: "Citrons verts (kg)", prix: 3.00, promo: null },
    { ref: "FRU-142", produit: "Bananes plantain (kg)", prix: 1.80, promo: -25 },
    { ref: "OEU-150", produit: "Œufs plein air (plateau 30)", prix: 8.40, promo: null },
  ],
  5: [ // Boissons Caraïbes
    { ref: "RHU-501", produit: "Rhum blanc agricole 50° (carton ×6)", prix: 82.00, promo: -10 },
    { ref: "RHU-512", produit: "Rhum vieux AOC (carton ×6)", prix: 186.00, promo: null },
    { ref: "BIE-530", produit: "Bières Lorraine 33cl (pack ×24)", prix: 24.50, promo: -12 },
    { ref: "SOF-545", produit: "Jus de goyave 1L (carton ×12)", prix: 28.80, promo: null },
    { ref: "CAF-560", produit: "Café grain 1kg", prix: 16.00, promo: null },
  ],
};

function Fournisseurs({ stocks, setStocks, precommandes, setPrecommandes, factures, setFactures }) {
  const [qtes, setQtes] = useState({});
  const [fournSel, setFournSel] = useState(null);
  const [scanEnCours, setScanEnCours] = useState(false);
  const [scanResultat, setScanResultat] = useState(null);
  const [syncPromos, setSyncPromos] = useState(false);
  const [derniereSync, setDerniereSync] = useState("aujourd'hui 06:00 (auto)");
  const scanRef = useRef(null);

  const aCommander = produitsACommander(stocks);
  const dejaEnCommande = new Set(precommandes.filter(p => p.statut === "envoyee").flatMap(p => p.items.map(i => i.stockId)));
  const suggestions = aCommander.filter(s => !dejaEnCommande.has(s.id));
  const promosActives = Object.entries(CATALOGUES).flatMap(([fid, items]) => items.filter(i => i.promo).map(i => ({ ...i, fid: Number(fid) })));

  // ---- SCAN DE FACTURE (vision IA → classement automatique) ----
  const scannerFacture = async (file) => {
    if (!file) return;
    setScanEnCours(true);
    setScanResultat(null);
    try {
      const base64 = await new Promise((res, rej) => {
        const r = new FileReader();
        r.onload = () => res(r.result.split(",")[1]);
        r.onerror = () => rej(new Error("e"));
        r.readAsDataURL(file);
      });
      const response = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          model: "claude-sonnet-4-20250514",
          max_tokens: 500,
          messages: [{
            role: "user",
            content: [
              { type: "image", source: { type: "base64", media_type: file.type || "image/jpeg", data: base64 } },
              { type: "text", text: `Ceci est une facture fournisseur de restaurant. Fournisseurs connus : ${FOURNISSEURS.map(f => f.nom).join(", ")}. Extrais les informations et réponds UNIQUEMENT avec un JSON valide sans backticks :
{"fournisseur":"<nom le plus proche parmi la liste, ou le nom lu si aucun ne correspond>","numero":"<n° de facture>","date":"<JJ/MM>","montantTTC":<nombre>}` }
            ],
          }],
        }),
      });
      const data = await response.json();
      const txt = (data.content || []).filter(b => b.type === "text").map(b => b.text).join("").replace(/```json|```/g, "").trim();
      const fac = JSON.parse(txt);
      const f = FOURNISSEURS.find(x => x.nom.toLowerCase() === String(fac.fournisseur).toLowerCase())
        || FOURNISSEURS.find(x => String(fac.fournisseur).toLowerCase().includes(x.nom.toLowerCase().split(" ")[0]));
      const nouvelle = {
        id: Date.now(), fid: f ? f.id : null, fournisseurLu: fac.fournisseur,
        numero: String(fac.numero || "—"), date: String(fac.date || "—"), montantTTC: Number(fac.montantTTC) || 0,
      };
      setFactures(prev => [...prev, nouvelle]);
      setScanResultat({ ok: true, msg: f
        ? `Facture ${nouvelle.numero} (${nouvelle.montantTTC.toFixed(2)} €) classée chez ${f.nom}`
        : `Facture lue (${fac.fournisseur}) — fournisseur inconnu, classée en "Non rattachées"` });
    } catch {
      setScanResultat({ ok: false, msg: "Lecture impossible — photo floue ? Réessaie." });
    }
    setScanEnCours(false);
  };

  // ---- SYNC PROMOTIONS (agent automatique — démo) ----
  const lancerSync = () => {
    setSyncPromos(true);
    setTimeout(() => {
      setSyncPromos(false);
      setDerniereSync("à l'instant (manuel)");
    }, 1600);
  };

  const creerPrecommande = (fid) => {
    const items = suggestions.filter(s => s.fid === fid).map(s => ({
      stockId: s.id, produit: s.produit, unite: s.unite, pu: s.pu,
      qte: parseFloat(String(qtes[s.id] ?? s.suggestion).replace(",", ".")) || s.suggestion,
    }));
    if (!items.length) return;
    setPrecommandes([...precommandes, {
      id: Date.now(), fid, items, statut: "envoyee",
      date: new Date().toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit" }),
    }]);
  };

  const receptionner = (cmd) => {
    setStocks(stocks.map(s => {
      const i = cmd.items.find(x => x.stockId === s.id);
      return i ? { ...s, qte: +(s.qte + i.qte).toFixed(2) } : s;
    }));
    setPrecommandes(precommandes.filter(p => p.id !== cmd.id));
  };

  const totalCmd = (items) => items.reduce((s, i) => s + i.qte * i.pu, 0);

  // ============ VUE DÉTAIL D'UN FOURNISSEUR ============
  if (fournSel) {
    const f = FOURNISSEURS.find(x => x.id === fournSel);
    const cat = CATALOGUES[f.id] || [];
    const promos = cat.filter(i => i.promo);
    const fact = factures.filter(x => x.fid === f.id);
    return (
      <div>
        <button onClick={() => setFournSel(null)} style={btnStyle(C.brunMoyen)}>← Tous les fournisseurs</button>
        <Carte style={{ marginTop: 12, borderTop: `4px solid ${C.or}` }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 6 }}>
            <div>
              <h2 style={{ fontFamily: "Fraunces, Georgia, serif", color: C.brun, margin: 0, fontSize: 22 }}>{f.nom}</h2>
              <div style={{ fontSize: 12, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 4 }}>{f.type} · 📞 {f.tel} · 🚚 {f.delai} · ★ {f.note}</div>
            </div>
            {promos.length > 0 && <Badge bg={C.rouille}>🔥 {promos.length} promo(s) en cours</Badge>}
          </div>
        </Carte>

        {/* Promotions */}
        {promos.length > 0 && (
          <Carte style={{ marginTop: 12, borderLeft: `4px solid ${C.rouille}` }}>
            <h3 style={{ ...h3Style, borderColor: C.rouille }}>Promotions en cours</h3>
            {promos.map(p => (
              <div key={p.ref} style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 13, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                <Badge bg={C.rouille}>{p.promo}%</Badge>
                <span style={{ flex: 1 }}>{p.produit}</span>
                <span style={{ textDecoration: "line-through", color: C.brunMoyen, fontSize: 11.5 }}>{p.prix.toFixed(2)} €</span>
                <b style={{ color: C.rouille }}>{(p.prix * (1 + p.promo / 100)).toFixed(2)} €</b>
              </div>
            ))}
          </Carte>
        )}

        {/* Catalogue */}
        <Carte style={{ marginTop: 12 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
            <h3 style={{ ...h3Style, margin: 0 }}>Catalogue</h3>
            <span style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.vert, fontWeight: 700 }}>● Actualisé {derniereSync}</span>
          </div>
          <div style={{ marginTop: 8 }}>
            {cat.map(p => (
              <div key={p.ref} style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                <span style={{ fontSize: 10, color: C.brunMoyen, minWidth: 56 }}>{p.ref}</span>
                <span style={{ flex: 1 }}>{p.produit}</span>
                {p.promo && <Badge bg={C.rouille}>{p.promo}%</Badge>}
                <b style={{ minWidth: 64, textAlign: "right", color: p.promo ? C.rouille : C.brun }}>
                  {(p.prix * (1 + (p.promo || 0) / 100)).toFixed(2)} €
                </b>
              </div>
            ))}
          </div>
        </Carte>

        {/* Factures classées */}
        <Carte style={{ marginTop: 12 }}>
          <h3 style={h3Style}>Factures ({fact.length})</h3>
          {!fact.length && <div style={{ fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontStyle: "italic" }}>Aucune facture scannée pour ce fournisseur. Utilise "📷 Scanner une facture" sur l'écran principal.</div>}
          {fact.map(x => (
            <div key={x.id} style={{ display: "flex", gap: 10, alignItems: "center", padding: "6px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
              <span style={{ color: C.brunMoyen, minWidth: 44 }}>{x.date}</span>
              <span style={{ flex: 1 }}>Facture n° {x.numero}</span>
              <b>{x.montantTTC.toFixed(2)} €</b>
            </div>
          ))}
          {fact.length > 0 && (
            <div style={{ display: "flex", justifyContent: "space-between", paddingTop: 8, fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun, fontSize: 14 }}>
              <span>Total période</span><span style={{ color: C.or }}>{fact.reduce((s, x) => s + x.montantTTC, 0).toFixed(2)} €</span>
            </div>
          )}
        </Carte>
      </div>
    );
  }

  // ============ VUE PRINCIPALE ============
  return (
    <div>
      {/* Barre d'actions : scan facture + sync promos */}
      <Carte style={{ marginBottom: 14, padding: 12 }}>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
          <input ref={scanRef} type="file" accept="image/*" capture="environment" style={{ display: "none" }}
            onChange={e => { scannerFacture(e.target.files?.[0]); e.target.value = ""; }} />
          <button onClick={() => scanRef.current?.click()} disabled={scanEnCours} style={{ ...btnStyle(C.or), marginTop: 0 }}>
            {scanEnCours ? "👁 L'IA lit la facture…" : "📷 Scanner une facture"}
          </button>
          <button onClick={lancerSync} disabled={syncPromos} style={{ ...btnStyle(C.brunMoyen), marginTop: 0 }}>
            {syncPromos ? "🔄 Synchronisation…" : "🔄 Récupérer les promotions"}
          </button>
          <span style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginLeft: "auto" }}>
            Catalogues & promos synchronisés chaque nuit à 06:00 · dernière sync : {derniereSync}
          </span>
        </div>
        {scanResultat && (
          <div style={{ marginTop: 8, padding: 8, borderRadius: 8, fontSize: 12, fontFamily: "Inter, system-ui, sans-serif",
            background: scanResultat.ok ? "#E3EBDD" : "#F3DCD4",
            border: `1.5px solid ${scanResultat.ok ? C.vert : C.rouille}`,
            color: scanResultat.ok ? C.vert : C.rouille, fontWeight: 700 }}>
            {scanResultat.ok ? "✓" : "⚠"} {scanResultat.msg}
          </div>
        )}
      </Carte>

      {/* Bandeau promotions toutes enseignes */}
      {promosActives.length > 0 && (
        <Carte style={{ marginBottom: 14, borderLeft: `4px solid ${C.rouille}` }}>
          <h3 style={{ ...h3Style, borderColor: C.rouille }}>🔥 Promotions du moment ({promosActives.length})</h3>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            {promosActives.map(p => (
              <button key={p.ref} onClick={() => setFournSel(p.fid)} style={{
                background: C.creme, border: `1.5px solid ${C.rouille}`, borderRadius: 10,
                padding: "8px 10px", cursor: "pointer", textAlign: "left", fontFamily: "Inter, system-ui, sans-serif",
              }}>
                <div style={{ fontSize: 11.5, fontWeight: 700, color: C.brun }}>{p.produit}</div>
                <div style={{ fontSize: 10.5, color: C.brunMoyen, marginTop: 2 }}>
                  {FOURNISSEURS.find(f => f.id === p.fid)?.nom} · <b style={{ color: C.rouille }}>{p.promo}%</b> → {(p.prix * (1 + p.promo / 100)).toFixed(2)} €
                </div>
              </button>
            ))}
          </div>
        </Carte>
      )}

      {/* Pré-commandes suggérées */}
      {suggestions.length > 0 && (
        <Carte style={{ marginBottom: 16, borderTop: `4px solid ${C.rouille}` }}>
          <h3 style={{ ...h3Style, borderColor: C.rouille }}>Pré-commandes suggérées · {suggestions.length} produit(s) à réapprovisionner</h3>
          <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginBottom: 10 }}>
            Détecté automatiquement depuis l'inventaire. Quantités pré-remplies pour recompléter à 2× le seuil — ajustables.
          </div>
          {FOURNISSEURS.filter(f => suggestions.some(s => s.fid === f.id)).map(f => {
            const lignes = suggestions.filter(s => s.fid === f.id);
            const total = lignes.reduce((s, l) => s + (parseFloat(String(qtes[l.id] ?? l.suggestion).replace(",", ".")) || l.suggestion) * l.pu, 0);
            return (
              <div key={f.id} style={{ marginBottom: 12, padding: 12, background: C.creme, borderRadius: 10, border: `1px solid ${C.orClair}` }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8, flexWrap: "wrap", gap: 6 }}>
                  <div>
                    <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun }}>{f.nom}</span>
                    <span style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginLeft: 8 }}>{f.type} · Livraison {f.delai}</span>
                  </div>
                  <Badge bg={C.rouille}>{lignes.length} produit(s)</Badge>
                </div>
                {lignes.map(l => (
                  <div key={l.id} style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                    <span style={{ flex: 1 }}>
                      {l.produit}
                      <span style={{ color: l.qte <= l.seuil ? C.rouille : C.or, fontSize: 10.5, fontWeight: 700, marginLeft: 6 }}>
                        {l.qte <= l.seuil ? "⚠ rupture" : "stock faible"} · reste {l.qte} {l.unite}
                      </span>
                    </span>
                    <input value={qtes[l.id] ?? l.suggestion} onChange={e => setQtes({ ...qtes, [l.id]: e.target.value })}
                      style={{ width: 52, padding: "5px 4px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, textAlign: "center", background: C.blanc, color: C.brun }} />
                    <span style={{ fontSize: 11, color: C.brunMoyen, minWidth: 28 }}>{l.unite}</span>
                    <span style={{ fontSize: 11.5, fontWeight: 700, color: C.brun, minWidth: 52, textAlign: "right" }}>
                      {((parseFloat(String(qtes[l.id] ?? l.suggestion).replace(",", ".")) || l.suggestion) * l.pu).toFixed(2)} €
                    </span>
                  </div>
                ))}
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 8 }}>
                  <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.or }}>Total : {total.toFixed(2)} €</span>
                  <button onClick={() => creerPrecommande(f.id)} style={{ ...btnStyle(C.vert), marginTop: 0 }}>✓ Créer la pré-commande</button>
                </div>
              </div>
            );
          })}
        </Carte>
      )}

      {/* Pré-commandes en cours */}
      {precommandes.length > 0 && (
        <Carte style={{ marginBottom: 16, borderTop: `4px solid ${C.vert}` }}>
          <h3 style={{ ...h3Style, borderColor: C.vert }}>Pré-commandes en cours</h3>
          {precommandes.map(cmd => {
            const f = FOURNISSEURS.find(x => x.id === cmd.fid);
            return (
              <div key={cmd.id} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderBottom: `1px dashed ${C.orClair}`, flexWrap: "wrap", gap: 8 }}>
                <div>
                  <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun }}>{f?.nom}</span>
                  <span style={{ fontSize: 11.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginLeft: 8 }}>
                    {cmd.date} · {cmd.items.length} produit(s) · {totalCmd(cmd.items).toFixed(2)} €
                  </span>
                  <div style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 2 }}>
                    {cmd.items.map(i => `${i.qte} ${i.unite} ${i.produit}`).join(" · ")}
                  </div>
                </div>
                <button onClick={() => receptionner(cmd)} style={{ ...btnStyle(C.or), marginTop: 0 }}>📦 Réceptionner (+ stock)</button>
              </div>
            );
          })}
        </Carte>
      )}

      {/* Carnet fournisseurs */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", gap: 12 }}>
        {FOURNISSEURS.map(f => {
          const nbSugg = suggestions.filter(s => s.fid === f.id).length;
          const nbPromos = (CATALOGUES[f.id] || []).filter(i => i.promo).length;
          const nbFact = factures.filter(x => x.fid === f.id).length;
          return (
            <Carte key={f.id} style={{ borderTop: `3px solid ${nbSugg ? C.rouille : C.brunMoyen}`, position: "relative", cursor: "pointer" }}>
              {nbSugg > 0 && (
                <span style={{ position: "absolute", top: -9, right: -7, minWidth: 24, height: 24, borderRadius: 12, background: C.rouille, color: C.blanc, fontSize: 12.5, fontWeight: 700, fontFamily: "Inter, system-ui, sans-serif", display: "flex", alignItems: "center", justifyContent: "center", padding: "0 6px", border: `2px solid ${C.blanc}` }}>{nbSugg}</span>
              )}
              <div onClick={() => setFournSel(f.id)}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <h3 style={{ fontFamily: "Fraunces, Georgia, serif", color: C.brun, margin: 0, fontSize: 17 }}>{f.nom}</h3>
                  <span style={{ color: C.or, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, fontSize: 13 }}>★ {f.note}</span>
                </div>
                <div style={{ fontSize: 12, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 4 }}>{f.type}</div>
                <div style={{ marginTop: 10, fontSize: 13, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, lineHeight: 1.7 }}>
                  📞 {f.tel}<br />🚚 Livraison {f.delai}
                </div>
                <div style={{ display: "flex", gap: 6, marginTop: 10, flexWrap: "wrap" }}>
                  {nbPromos > 0 && <Badge bg={C.rouille}>🔥 {nbPromos} promo(s)</Badge>}
                  <Badge color={C.brunMoyen}>{nbFact} facture(s)</Badge>
                  <Badge color={C.or}>Catalogue →</Badge>
                </div>
              </div>
            </Carte>
          );
        })}
      </div>
      {factures.some(x => !x.fid) && (
        <Carte style={{ marginTop: 12, borderLeft: `4px solid ${C.or}` }}>
          <h3 style={h3Style}>Factures non rattachées</h3>
          {factures.filter(x => !x.fid).map(x => (
            <div key={x.id} style={{ display: "flex", gap: 10, alignItems: "center", padding: "5px 0", fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
              <span style={{ color: C.brunMoyen }}>{x.date}</span>
              <span style={{ flex: 1 }}>{x.fournisseurLu} · n° {x.numero}</span>
              <b>{x.montantTTC.toFixed(2)} €</b>
            </div>
          ))}
        </Carte>
      )}
    </div>
  );
}

function AssistantIA({ stocks, setStocks, releves, setReleves, planning, setPlanning, menu, setMenu, tables, setTables, commandes, setCommandes, mep, setMep, precommandes, setPrecommandes, ventes, rapportsZ, factures, reservations, setReservations, naviguer, ajouterFiche, ordreInitial, consommerOrdre }) {
  const [messages, setMessages] = useState([
    { role: "assistant", content: "Bonjour Chef ! Je pilote votre établissement : je peux consulter ET agir sur tous vos modules. Essayez : « enregistre 2,5°C pour la vitrine », « sors 2 kg de riz du stock », « compose un menu du jour à 26 € avec entrée, plat, dessert », « mets Kévin en repos samedi », ou « ouvre le module stocks »." },
  ]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [ecoute, setEcoute] = useState(false);
  const [voixActive, setVoixActive] = useState(true);
  const [microDispo, setMicroDispo] = useState(true);
  const endRef = useRef(null);
  useEffect(() => { endRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]);
  // Ordre lancé depuis la barre de l'accueil : exécuté à l'ouverture du pilote
  useEffect(() => {
    if (ordreInitial) { const o = ordreInitial; consommerOrdre(); envoyer(o); }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const [fichesAjoutees, setFichesAjoutees] = useState(() => new Set());
  const ajouterProposition = (p) => {
    const coutTotal = p.ingredients.reduce((s, i) => s + (Number(i.cout) || 0), 0);
    const fiche = {
      id: Math.max(...FICHES.map(f => f.id)) + 1,
      nom: p.nom, description: p.description ? String(p.description) : "", categorie: p.categorie || "Plat", portions: Number(p.portions) || 4,
      coutPortion: +(coutTotal / (Number(p.portions) || 4)).toFixed(2),
      prixVente: Number(p.prixVente) || 0,
      tempsPrepa: p.tempsPrepa || "—", tempsCuisson: p.tempsCuisson || "—",
      ingredients: p.ingredients.map(i => ({ nom: String(i.nom), qte: String(i.qte), cout: Number(i.cout) || 0 })),
      etapes: p.etapes.map(String),
      allergenes: Array.isArray(p.allergenes) && p.allergenes.length ? p.allergenes.map(String) : ["Aucun allergène majeur"],
      dressage: Array.isArray(p.dressage) ? p.dressage.map(d => ({ element: String(d.element), qte: String(d.qte) })) : [],
    };
    ajouterFiche(fiche);
    setFichesAjoutees(prev => new Set(prev).add(p.nom));
  };

  const lireVoixHaute = (texte) => {
    if (!voixActive || !("speechSynthesis" in window)) return;
    window.speechSynthesis.cancel();
    const u = new SpeechSynthesisUtterance(texte.replace(/[▤❄✎◷⬚📦✓✗→•]/g, " "));
    u.lang = "fr-FR";
    window.speechSynthesis.speak(u);
  };

  const demarrerMicro = () => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) { setMicroDispo(false); return; }
    try {
      const rec = new SR();
      rec.lang = "fr-FR";
      rec.interimResults = false;
      rec.onresult = (e) => { const txt = e.results[0][0].transcript; setInput(txt); envoyer(txt); };
      rec.onend = () => setEcoute(false);
      rec.onerror = () => { setEcoute(false); setMicroDispo(false); };
      setEcoute(true);
      rec.start();
    } catch { setEcoute(false); setMicroDispo(false); }
  };

  // ---- ÉTAT TEMPS RÉEL DE L'ÉTABLISSEMENT (recalculé à chaque message) ----
  const construireContexte = () => `Tu es l'assistant-pilote de "Mizen Pro", application modulaire de gestion de restaurant en Martinique. Tu réponds en français, concis, ton professionnel chaleureux ("Chef"). Tu peux LIRE l'état ci-dessous et AGIR via le protocole d'actions.

=== ÉTAT TEMPS RÉEL ===
CARTE (${FICHES.length} fiches) : ${FICHES.map(f => `${f.nom} [${f.categorie}] coût ${f.coutPortion}€ / PV ${f.prixVente}€ / FC ${(f.coutPortion / f.prixVente * 100).toFixed(0)}% / allergènes: ${f.allergenes.join("+")}`).join(" | ")}
HACCP : ${releves.map(r => `${r.equipement}: ${r.valeur}°C à ${r.heure} (cible ${r.cible}) → ${r.statut.toUpperCase()}`).join(" | ")}
STOCKS : ${stocks.map(s => `${s.produit}: ${s.qte} ${s.unite} (seuil ${s.seuil}, ${s.pu}€/${s.unite}, ${s.zone})${s.qte <= s.seuil ? " ⚠RUPTURE" : ""}`).join(" | ")}
PLANNING SEMAINE (jours: ${JOURS.join(",")}) : ${EQUIPE.map(e => `${e.nom} (${e.poste}): ${planning[e.id].join("/")}`).join(" | ")}
MENU EN COURS : "${menu.titre}" à ${menu.prix}€ avec [${FICHES.filter(f => menu.selection.includes(f.id)).map(f => f.nom).join(", ") || "vide"}]
MISE EN PLACE (dispo à la vente, DLC J+3 après production) : ${FICHES.map(f => { const m = mep[f.id]; if (!m?.valide) return `${f.nom}: non validé`; const jr = Math.ceil(((m.prodLe || Date.now()) + 3 * 86400000 - Date.now()) / 86400000); return `${f.nom}: ${m.dispo} dispo (DLC ${jr <= 0 ? "DÉPASSÉE" : "J-" + jr})`; }).join(" | ")}
SALLE : ${tables.map(t => `Table ${t.num} (${t.places} cv): ${t.statut}`).join(" | ")}
RÉSERVATIONS DU JOUR : ${reservations.filter(r => r.statut !== "annulee").map(r => `${r.heure} ${r.nom} (${r.couverts} cv, ${r.service}${r.table ? ", T" + r.table : ""}, ${r.statut})`).join(" | ") || "aucune"}
COMMANDES EN COURS : ${commandes.filter(c => c.statut !== "servie").map(c => `#T${c.table} ${c.heure} [${c.items.map(i => i.qte + "x " + (FICHES.find(f => f.id === i.ficheId)?.nom || "?")).join(", ")}] → ${c.statut}`).join(" | ") || "aucune"}
FOURNISSEURS : ${FOURNISSEURS.map(f => `${f.nom} (${f.type}, ${f.delai})`).join(" | ")}
À RÉAPPROVISIONNER : ${produitsACommander(stocks).map(s => `${s.produit} (reste ${s.qte} ${s.unite}, suggéré ${s.suggestion} ${s.unite}, chez ${FOURNISSEURS.find(f => f.id === s.fid)?.nom})`).join(" | ") || "rien"}
PROMOS FOURNISSEURS : ${Object.entries(CATALOGUES).flatMap(([fid, items]) => items.filter(i => i.promo).map(i => `${i.produit} ${i.promo}% chez ${FOURNISSEURS.find(f => f.id === Number(fid))?.nom}`)).join(" | ")}
FACTURES SCANNÉES : ${factures.length} (total ${factures.reduce((s, x) => s + x.montantTTC, 0).toFixed(2)}€)
PRÉ-COMMANDES EN COURS : ${precommandes.map(p => `${FOURNISSEURS.find(f => f.id === p.fid)?.nom}: ${p.items.length} produit(s)`).join(" | ") || "aucune"}
CAISSE DU JOUR : ${ventes.length} ticket(s), CA ${ventes.reduce((s, v) => s + v.total, 0).toFixed(2)}€ (${ventes.filter(v => v.origine === "Comptoir").length} comptoir / ${ventes.filter(v => v.origine !== "Comptoir").length} tables)
HISTORIQUE Z (clôtures) : ${rapportsZ.slice(-7).map(z => `${z.date}: ${z.caTTC.toFixed(0)}€/${z.couverts}cv`).join(" | ") || "aucun"}
SYNTHÈSE DU MOIS : CA ${rapportsZ.reduce((s, z) => s + z.caTTC, 0).toFixed(0)}€ sur ${rapportsZ.length} services, ${rapportsZ.reduce((s, z) => s + z.couverts, 0)} couverts, vs ${MOIS_PRECEDENT.libelle} ${MOIS_PRECEDENT.caTTC.toFixed(0)}€
VENTES DU MOIS PAR PLAT : ${FICHES.map(f => `${f.nom}: ${STATS_PLATS_INIT[f.id] || 0}`).join(" | ")}
FOOD COST GLOBAL (théorique, pondéré ventes) : ${(() => { let ca = 0, ct = 0; for (const f of FICHES) { const v = STATS_PLATS_INIT[f.id] || 0; ca += f.prixVente * v; ct += f.coutPortion * v; } return ca ? (ct / ca * 100).toFixed(1) : "0"; })()}% — Objectif usuel 28-32%. Pour le menu engineering : star = populaire+marge haute, cheval de labour = populaire+marge faible, énigme = peu vendu+marge haute, poids mort = ni l'un ni l'autre.
MODULES : dashboard, fiches, stocks, carte, caisse, haccp, fournisseurs, planning, service, ia, boutique

=== PROTOCOLE D'ACTIONS ===
Pour AGIR, termine ta réponse par un bloc EXACTEMENT de cette forme (JSON valide, rien après) :
@@ACTIONS
[{"type":"...", ...}]
@@FIN
Types disponibles :
- {"type":"stock","produit":"<nom exact>","delta":<nombre ±>} → ajuste une quantité (delta négatif = sortie)
- {"type":"haccp","equipement":"<nom exact>","valeur":<°C>} → enregistre un relevé de température
- {"type":"menu","titre":"<titre>","prix":<nombre>,"plats":["<nom exact de fiche>", ...]} → compose le menu du jour
- {"type":"planning","employe":"<prénom exact>","jour":"<Lun|Mar|Mer|Jeu|Ven|Sam|Dim>","service":"<Repos|Midi|Soir|Doublon>"} → modifie le planning
- {"type":"commande","table":<numéro>,"plats":[{"nom":"<nom exact de fiche>","qte":<n>}, ...]} → envoie une commande en cuisine (refuse si plat épuisé en mise en place : signale-le au Chef)
- {"type":"mep","plat":"<nom exact de fiche>","portions":<n>} → valide la mise en place d'un plat (déduit les matières premières de l'inventaire)
- {"type":"precommande","fournisseur":"<nom exact>"} → crée une pré-commande chez ce fournisseur avec tous ses produits à réapprovisionner (quantités suggérées)
- {"type":"reservation","nom":"<nom>","couverts":<n>,"heure":"<HH:MM>","service":"<Midi|Soir>","tel":"<optionnel>"} → ajoute une réservation au carnet
- {"type":"naviguer","module":"<id module>"} → ouvre un module pour le Chef
Règles : n'émets des actions QUE si le Chef demande une modification ou si c'est clairement utile (annonce-le dans ta réponse texte). Utilise les noms EXACTS de l'état. Plusieurs actions possibles dans le même bloc. Si une action HACCP est non conforme, signale l'alerte et l'action corrective réglementaire (PMS). Réponses texte : 3-7 phrases max.

=== PROPOSITION DE FICHE TECHNIQUE ===
Quand le Chef te demande une recette, une idée de plat, ou un nouveau plat (qui n'est pas déjà dans la carte), donne une réponse texte courte (présentation du plat, 2-4 phrases) PUIS ajoute un bloc EXACTEMENT de cette forme :
@@FICHE
{"nom":"...","description":"<courte description appétissante pour la carte, 8-15 mots>","categorie":"Entrée|Plat|Accompagnement|Dessert","portions":<n>,"tempsPrepa":"...","tempsCuisson":"...","prixVente":<€>,"ingredients":[{"nom":"...","qte":"...","cout":<€ estimé Martinique pour le total>}],"etapes":["..."],"allergenes":["..."],"dressage":[{"element":"<composant de l'assiette>","qte":"<quantité par portion, ex: 150 g / 2 pièces / PM>"}]}
@@FINFICHE
Ce bloc s'affichera comme une carte que le Chef peut ajouter à ses fiches techniques en un clic. Quantités professionnelles précises (g/kg/ml/L/pièces), coûts réalistes pour la Martinique, 4-6 étapes claires, allergènes réglementaires (ou ["Aucun allergène majeur"]). N'utilise PAS ce bloc pour les recettes déjà présentes dans la carte.`;

  // ---- EXÉCUTEUR D'ACTIONS ----
  const executer = (actions) => {
    const faits = [];
    let nvStocks = stocks, nvReleves = releves, nvPlanning = planning, nvMenu = menu;
    for (const a of actions) {
      try {
        if (a.type === "stock") {
          const p = nvStocks.find(s => s.produit.toLowerCase() === String(a.produit).toLowerCase());
          if (!p) { faits.push(`✗ Stock introuvable : ${a.produit}`); continue; }
          nvStocks = nvStocks.map(s => s.id === p.id ? { ...s, qte: Math.max(0, +(s.qte + Number(a.delta)).toFixed(2)) } : s);
          faits.push(`▤ Stock ${p.produit} : ${a.delta > 0 ? "+" : ""}${a.delta} ${p.unite}`);
        } else if (a.type === "haccp") {
          const r = nvReleves.find(x => x.equipement.toLowerCase() === String(a.equipement).toLowerCase());
          if (!r) { faits.push(`✗ Équipement introuvable : ${a.equipement}`); continue; }
          const v = Number(a.valeur);
          const ok = r.cible.includes("min") ? v >= 63 : r.cible.includes("-18") ? v <= -18 : v >= 0 && v <= 4;
          nvReleves = nvReleves.map(x => x.id === r.id ? { ...x, valeur: v, heure: new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }), statut: ok ? "ok" : "alerte" } : x);
          faits.push(`❄ Relevé ${r.equipement} : ${v}°C → ${ok ? "conforme" : "⚠ ALERTE"}`);
        } else if (a.type === "menu") {
          const ids = (a.plats || []).map(nom => FICHES.find(f => f.nom.toLowerCase() === String(nom).toLowerCase())?.id).filter(Boolean);
          nvMenu = { titre: a.titre || nvMenu.titre, prix: String(a.prix ?? nvMenu.prix), selection: ids.length ? ids : nvMenu.selection };
          faits.push(`✎ Menu "${nvMenu.titre}" composé (${ids.length} plats, ${nvMenu.prix} €)`);
        } else if (a.type === "planning") {
          const e = EQUIPE.find(x => x.nom.toLowerCase() === String(a.employe).toLowerCase());
          const j = JOURS.findIndex(x => x.toLowerCase() === String(a.jour).toLowerCase().slice(0, 3));
          if (!e || j < 0 || !SERVICES.includes(a.service)) { faits.push(`✗ Planning invalide : ${a.employe}/${a.jour}/${a.service}`); continue; }
          const nv = { ...nvPlanning, [e.id]: [...nvPlanning[e.id]] };
          nv[e.id][j] = a.service;
          nvPlanning = nv;
          faits.push(`◷ ${e.nom} → ${a.service} ${JOURS[j]}`);
        } else if (a.type === "commande") {
          const t = tables.find(x => x.num === Number(a.table));
          if (!t) { faits.push(`✗ Table introuvable : ${a.table}`); continue; }
          const items = (a.plats || []).map(p => ({ ficheId: FICHES.find(f => f.nom.toLowerCase() === String(p.nom).toLowerCase())?.id, qte: Number(p.qte) || 1 })).filter(i => i.ficheId);
          if (!items.length) { faits.push(`✗ Aucun plat reconnu pour la table ${a.table}`); continue; }
          const epuises = items.filter(i => mep[i.ficheId]?.valide && mep[i.ficheId].dispo < i.qte);
          if (epuises.length) { faits.push(`✗ Épuisé/insuffisant : ${epuises.map(i => FICHES.find(f => f.id === i.ficheId)?.nom).join(", ")}`); continue; }
          setCommandes(prev => [...prev, { id: Date.now() + Math.random(), table: t.num, items, statut: "en_attente", heure: new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }), ts: Date.now() }]);
          setTables(prev => prev.map(x => x.num === t.num ? { ...x, statut: "occupee" } : x));
          setMep(prev => {
            const nv = { ...prev };
            for (const i of items) if (nv[i.ficheId]?.valide) nv[i.ficheId] = { ...nv[i.ficheId], dispo: Math.max(0, nv[i.ficheId].dispo - i.qte) };
            return nv;
          });
          faits.push(`⬚ Commande table ${t.num} envoyée en cuisine (${items.length} ligne(s))`);
        } else if (a.type === "mep") {
          const f = FICHES.find(x => x.nom.toLowerCase() === String(a.plat).toLowerCase());
          if (!f) { faits.push(`✗ Fiche introuvable : ${a.plat}`); continue; }
          const portions = Number(a.portions) || 0;
          if (portions <= 0) { faits.push(`✗ Portions invalides pour ${f.nom}`); continue; }
          const deductions = calculerDeductions(stocks, f, portions);
          setStocks(prev => prev.map(s => {
            const d = deductions.find(x => x.stockId === s.id);
            return d ? { ...s, qte: Math.max(0, +(s.qte - d.qte).toFixed(2)) } : s;
          }));
          setMep(prev => ({ ...prev, [f.id]: { valide: true, dispo: portions, deductions, prodLe: Date.now() } }));
          faits.push(`✓ MEP ${f.nom} : ${portions} portions (matières déduites de l'inventaire)`);
        } else if (a.type === "precommande") {
          const f = FOURNISSEURS.find(x => x.nom.toLowerCase() === String(a.fournisseur).toLowerCase());
          if (!f) { faits.push(`✗ Fournisseur introuvable : ${a.fournisseur}`); continue; }
          const dejaCmd = new Set(precommandes.flatMap(p => p.items.map(i => i.stockId)));
          const lignes = produitsACommander(nvStocks).filter(s => s.fid === f.id && !dejaCmd.has(s.id))
            .map(s => ({ stockId: s.id, produit: s.produit, unite: s.unite, pu: s.pu, qte: s.suggestion }));
          if (!lignes.length) { faits.push(`✗ Rien à commander chez ${f.nom}`); continue; }
          setPrecommandes(prev => [...prev, { id: Date.now() + Math.random(), fid: f.id, items: lignes, statut: "envoyee", date: new Date().toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit" }) }]);
          faits.push(`📦 Pré-commande ${f.nom} : ${lignes.length} produit(s), ${lignes.reduce((s, l) => s + l.qte * l.pu, 0).toFixed(2)} €`);
        } else if (a.type === "reservation") {
          if (!a.nom || !a.heure) { faits.push("✗ Réservation incomplète (nom + heure requis)"); continue; }
          setReservations(prev => [...prev, {
            id: Date.now() + Math.random(), nom: String(a.nom), couverts: Number(a.couverts) || 2,
            heure: String(a.heure), service: a.service === "Midi" ? "Midi" : "Soir",
            tel: String(a.tel || ""), table: null, statut: "a_venir",
          }]);
          faits.push(`✆ Réservation ${a.nom} · ${a.couverts || 2} cv · ${a.heure} (${a.service || "Soir"})`);
        } else if (a.type === "naviguer") {
          naviguer(String(a.module));
          faits.push(`→ Module ${a.module} ouvert`);
        }
      } catch { faits.push("✗ Action invalide ignorée"); }
    }
    setStocks(nvStocks); setReleves(nvReleves); setPlanning(nvPlanning); setMenu(nvMenu);
    return faits;
  };

  const envoyer = async (texteForce) => {
    const contenu = String(texteForce ?? input).trim();
    if (!contenu || loading) return;
    const userMsg = { role: "user", content: contenu };
    const histo = [...messages, userMsg];
    setMessages(histo);
    setInput("");
    setLoading(true);
    try {
      const response = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          model: "claude-sonnet-4-20250514",
          max_tokens: 1500,
          system: construireContexte(),
          messages: histo.filter(m => !m.actions).map(m => ({ role: m.role, content: m.content })),
        }),
      });
      const data = await response.json();
      const brut = (data.content || []).filter(b => b.type === "text").map(b => b.text).join("\n") || "Désolé Chef, je n'ai pas pu répondre.";
      // Extraire le bloc d'actions éventuel
      let texte = brut, faits = [], proposition = null;
      const match = brut.match(/@@ACTIONS\s*([\s\S]*?)\s*@@FIN/);
      if (match) {
        texte = texte.replace(match[0], "").trim();
        try {
          const actions = JSON.parse(match[1].replace(/```json|```/g, "").trim());
          faits = executer(Array.isArray(actions) ? actions : [actions]);
        } catch { faits = ["✗ Bloc d'actions illisible — rien n'a été modifié"]; }
      }
      // Extraire une proposition de fiche technique éventuelle
      const matchFiche = texte.match(/@@FICHE\s*([\s\S]*?)\s*@@FINFICHE/);
      if (matchFiche) {
        texte = texte.replace(matchFiche[0], "").trim();
        try {
          const f = JSON.parse(matchFiche[1].replace(/```json|```/g, "").trim());
          if (f.nom && Array.isArray(f.ingredients) && Array.isArray(f.etapes)) proposition = f;
        } catch { /* proposition illisible : on garde juste le texte */ }
      }
      setMessages([...histo, { role: "assistant", content: texte, actions: faits, proposition }]);
      lireVoixHaute(faits.length ? texte + ". " + faits.join(". ") : texte);
    } catch {
      setMessages([...histo, { role: "assistant", content: "Erreur de connexion à l'assistant. Réessaie." }]);
    }
    setLoading(false);
  };

  const suggestions = [
    "Propose-moi une recette de ti-punch revisité en dessert",
    "Enregistre 2,5°C pour la vitrine réfrigérée",
    "Compose un menu créole à 26 €",
    "Qu'est-ce que je dois commander cette semaine ?",
  ];

  return (
    <Carte style={{ display: "flex", flexDirection: "column", height: "62vh", minHeight: 420, padding: 0, overflow: "hidden" }}>
      <div style={{ background: C.brun, color: C.creme, padding: "12px 16px", fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, display: "flex", alignItems: "center", gap: 8 }}>
        <span style={{ width: 9, height: 9, borderRadius: "50%", background: ecoute ? "#E08A6D" : "#7BC47F", display: "inline-block" }} />
        <span style={{ flex: 1 }}>Pilote Mizen Pro — texte & voix, agit sur tous vos modules</span>
        <button onClick={() => { if (voixActive) window.speechSynthesis?.cancel(); setVoixActive(!voixActive); }} title={voixActive ? "Couper la voix" : "Activer la voix"} style={{
          background: "transparent", border: `1.5px solid ${C.orClair}`, borderRadius: 14,
          color: voixActive ? C.orClair : "#9b8a72", fontSize: 11.5, fontWeight: 700,
          padding: "4px 10px", cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
        }}>{voixActive ? "🔊 Voix ON" : "🔇 Voix OFF"}</button>
      </div>
      <div style={{ flex: 1, overflowY: "auto", padding: 14, background: C.creme }}>
        {messages.map((m, i) => (
          <div key={i} style={{ marginBottom: 10 }}>
            <div style={{ display: "flex", justifyContent: m.role === "user" ? "flex-end" : "flex-start" }}>
              <div style={{
                maxWidth: "82%", padding: "10px 13px", borderRadius: 12,
                background: m.role === "user" ? C.or : C.blanc,
                color: m.role === "user" ? C.blanc : C.brun,
                border: m.role === "user" ? "none" : `1px solid ${C.orClair}`,
                fontFamily: "Inter, system-ui, sans-serif", fontSize: 13.5, lineHeight: 1.55, whiteSpace: "pre-wrap",
              }}>{m.content}</div>
            </div>
            {m.proposition && (
              <div style={{ marginTop: 8, maxWidth: "82%", padding: 12, background: C.blanc, border: `2px solid ${C.or}`, borderRadius: 12 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 6 }}>
                  <div>
                    <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun, fontSize: 15 }}>✦ {m.proposition.nom}</div>
                    <div style={{ fontSize: 11, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginTop: 2 }}>
                      {m.proposition.categorie} · {m.proposition.portions} portions · {m.proposition.ingredients.length} ingrédients · PV {Number(m.proposition.prixVente).toFixed(2)} €
                    </div>
                  </div>
                  {fichesAjoutees.has(m.proposition.nom) ? (
                    <Badge bg={C.vert}>✓ Dans vos fiches</Badge>
                  ) : (
                    <button onClick={() => ajouterProposition(m.proposition)} style={{ ...btnStyle(C.vert), marginTop: 0 }}>➕ Ajouter aux fiches techniques</button>
                  )}
                </div>
              </div>
            )}
            {m.actions && m.actions.length > 0 && (
              <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 }}>
                {m.actions.map((f, k) => (
                  <span key={k} style={{
                    fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, padding: "4px 10px", borderRadius: 12,
                    background: f.startsWith("✗") ? "#F3DCD4" : "#E3EBDD",
                    color: f.startsWith("✗") ? C.rouille : C.vert,
                    border: `1px solid ${f.startsWith("✗") ? C.rouille : C.vert}`,
                  }}>{f}</span>
                ))}
              </div>
            )}
          </div>
        ))}
        {loading && <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, color: C.brunMoyen, fontStyle: "italic" }}>Le pilote agit…</div>}
        <div ref={endRef} />
      </div>
      <div style={{ padding: 10, borderTop: `1px solid ${C.orClair}`, background: C.blanc }}>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 8 }}>
          {suggestions.map((s, i) => (
            <button key={i} onClick={() => setInput(s)} style={{ ...btnStyle(C.brunMoyen), fontSize: 11, padding: "4px 10px" }}>{s}</button>
          ))}
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <button onClick={demarrerMicro} disabled={ecoute || loading} title={microDispo ? "Parler au pilote" : "Micro indisponible dans cet aperçu"} style={{
            width: 44, height: 44, borderRadius: "50%", flexShrink: 0,
            border: `2.5px solid ${C.or}`, cursor: microDispo ? "pointer" : "default",
            background: ecoute ? C.rouille : microDispo ? C.brun : C.cremeFonce,
            color: microDispo ? C.creme : C.brunMoyen, fontSize: 19,
            boxShadow: ecoute ? "0 0 0 8px rgba(139,58,30,0.18)" : "none", transition: "all .2s",
          }}>{ecoute ? "…" : "🎙"}</button>
          <input
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => e.key === "Enter" && envoyer()}
            placeholder={ecoute ? "Je t'écoute, Chef…" : "Parle ou écris ton ordre, Chef…"}
            style={{ flex: 1, padding: "10px 12px", border: `1.5px solid ${C.orClair}`, borderRadius: 8, fontFamily: "Inter, system-ui, sans-serif", fontSize: 14, background: C.blanc, color: C.brun, outline: "none" }}
          />
          <button onClick={() => envoyer()} disabled={loading} style={{ ...btnStyle(C.or), padding: "10px 18px", marginTop: 0, opacity: loading ? 0.6 : 1 }}>Envoyer</button>
        </div>
        {!microDispo && <div style={{ fontSize: 10.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 5 }}>Micro bloqué dans cet aperçu (restriction navigateur) — la dictée fonctionnera dans l'app finale. La voix de synthèse reste active.</div>}
      </div>
    </Carte>
  );
}

// ============ STYLES UTILES ============
const btnStyle = (couleur) => ({
  background: couleur, color: C.blanc, border: "none", borderRadius: 9,
  padding: "8px 15px", fontSize: 12, fontWeight: 600, cursor: "pointer",
  fontFamily: "Inter, system-ui, sans-serif", marginTop: 6,
  letterSpacing: 0.1, boxShadow: "0 1px 3px rgba(53,39,24,0.18)",
});
const miniBtn = {
  width: 28, height: 28, borderRadius: "50%", border: `1.5px solid ${C.or}`,
  background: C.blanc, color: C.or, fontSize: 16, fontWeight: 700, cursor: "pointer",
};
const h3Style = { fontFamily: "Fraunces, Georgia, serif", color: C.brun, fontSize: 15.5, fontWeight: 600, letterSpacing: 0.2, margin: "0 0 10px", borderBottom: `2px solid ${C.or}`, paddingBottom: 6 };

// ============ EXTENSION : STOCKS & INVENTAIRE ============
const STOCKS_INIT = [
  // --- CUISINE (matières premières) ---
  { id: 1, fid: 1, produit: "Cuisses de poulet", unite: "kg", qte: 8.5, seuil: 5, pu: 4.5, zone: "CF négative", pole: "Cuisine" },
  { id: 2, fid: 2, produit: "Morue salée", unite: "kg", qte: 6.5, seuil: 3, pu: 9.0, zone: "Sec", pole: "Cuisine" },
  { id: 3, fid: 3, produit: "Lait de coco", unite: "L", qte: 12, seuil: 6, pu: 5.2, zone: "Sec", pole: "Cuisine" },
  { id: 4, fid: 4, produit: "Christophines", unite: "kg", qte: 8.2, seuil: 4, pu: 2.4, zone: "CF positive", pole: "Cuisine" },
  { id: 5, fid: 3, produit: "Riz long grain", unite: "kg", qte: 22, seuil: 10, pu: 1.6, zone: "Sec", pole: "Cuisine" },
  { id: 6, fid: 2, produit: "Crabes de terre", unite: "pièce", qte: 14, seuil: 10, pu: 2.4, zone: "CF négative", pole: "Cuisine" },
  { id: 7, fid: 4, produit: "Citrons verts", unite: "kg", qte: 3.8, seuil: 2, pu: 3.0, zone: "CF positive", pole: "Cuisine" },
  { id: 8, fid: 3, produit: "Huile de friture", unite: "L", qte: 15, seuil: 8, pu: 1.8, zone: "Sec", pole: "Cuisine" },
  { id: 9, fid: 3, produit: "Farine T55", unite: "kg", qte: 18, seuil: 8, pu: 1.2, zone: "Sec", pole: "Cuisine" },
  { id: 10, fid: 4, produit: "Œufs", unite: "pièce", qte: 90, seuil: 60, pu: 0.3, zone: "CF positive", pole: "Cuisine" },
  { id: 11, fid: 3, produit: "Lait concentré sucré", unite: "kg", qte: 6, seuil: 3, pu: 4.2, zone: "Sec", pole: "Cuisine" },
  { id: 12, fid: 2, produit: "Vivaneau", unite: "kg", qte: 7, seuil: 4, pu: 11.0, zone: "CF négative", pole: "Cuisine" },
  // --- SALLE (consommables) ---
  { id: 20, fid: 3, produit: "Serviettes papier", unite: "pièce", qte: 850, seuil: 300, pu: 0.03, zone: "Réserve salle", pole: "Salle" },
  { id: 21, fid: 3, produit: "Sets de table", unite: "pièce", qte: 420, seuil: 200, pu: 0.08, zone: "Réserve salle", pole: "Salle" },
  { id: 22, fid: 3, produit: "Nappes papier (rouleau)", unite: "pièce", qte: 4, seuil: 2, pu: 12.5, zone: "Réserve salle", pole: "Salle" },
  { id: 23, fid: 3, produit: "Produit vitres & tables", unite: "L", qte: 3, seuil: 2, pu: 4.8, zone: "Local entretien", pole: "Salle" },
  { id: 24, fid: 3, produit: "Bougies chauffe-plat", unite: "pièce", qte: 60, seuil: 30, pu: 0.15, zone: "Réserve salle", pole: "Salle" },
  // --- BAR ---
  { id: 30, fid: 5, produit: "Rhum blanc agricole 50°", unite: "pièce", qte: 9, seuil: 4, pu: 14.5, zone: "Bar", pole: "Bar" },
  { id: 31, fid: 5, produit: "Rhum vieux AOC", unite: "pièce", qte: 5, seuil: 2, pu: 32.0, zone: "Bar", pole: "Bar" },
  { id: 32, fid: 5, produit: "Sirop de canne", unite: "pièce", qte: 6, seuil: 3, pu: 3.8, zone: "Bar", pole: "Bar" },
  { id: 33, fid: 5, produit: "Jus de goyave", unite: "L", qte: 14, seuil: 6, pu: 2.6, zone: "Bar (CF)", pole: "Bar" },
  { id: 34, fid: 5, produit: "Bières Lorraine 33cl", unite: "pièce", qte: 72, seuil: 48, pu: 1.1, zone: "Bar (CF)", pole: "Bar" },
  { id: 35, fid: 5, produit: "Café grain", unite: "kg", qte: 4, seuil: 2, pu: 16.0, zone: "Bar", pole: "Bar" },
  { id: 36, fid: 5, produit: "Eau pétillante 75cl", unite: "pièce", qte: 36, seuil: 24, pu: 0.9, zone: "Bar (CF)", pole: "Bar" },
];

// ---- MOTEUR DE DÉDUCTION : consommation matière par portion ----
// Parse "2,5 kg" / "400 ml" / "8 pièces" → quantité en unité de stock (kg, L, pièce)
function parseQuantite(txt) {
  const m = String(txt).replace(",", ".").match(/([\d.]+)\s*(kg|g|l|ml|cl|pièces?|×)?/i);
  if (!m) return null;
  let v = parseFloat(m[1]);
  const u = (m[2] || "").toLowerCase();
  if (u === "g") return { v: v / 1000, u: "kg" };
  if (u === "kg") return { v, u: "kg" };
  if (u === "ml") return { v: v / 1000, u: "L" };
  if (u === "cl") return { v: v / 100, u: "L" };
  if (u === "l") return { v, u: "L" };
  return { v, u: "pièce" };
}
// Adapte une quantité texte ("2,5 kg", "8 pièces", "400 ml") à un ratio de portions
function adapterQte(txt, ratio) {
  if (ratio === 1 || !/[\d]/.test(String(txt))) return txt; // "PM" et autres restent tels quels
  let premier = true;
  return String(txt).replace(/(\d+[.,]?\d*)/, (m) => {
    if (!premier) return m;
    premier = false;
    const v = parseFloat(m.replace(",", ".")) * ratio;
    const arrondi = v >= 100 ? Math.round(v) : Math.round(v * 100) / 100;
    return String(arrondi).replace(".", ",");
  });
}

// Rapproche un ingrédient d'un produit du stock par préfixe de mots (≥4 lettres)
function trouverStock(stocks, nomIngredient) {
  const mots = nomIngredient.toLowerCase().split(/[\s,()]+/).filter(w => w.length >= 4);
  return stocks.find(s => {
    const motsStock = s.produit.toLowerCase().split(/[\s,()]+/).filter(w => w.length >= 4);
    return mots.some(mi => motsStock.some(ms => ms.startsWith(mi.slice(0, 4)) && mi.startsWith(ms.slice(0, 4))));
  });
}
// Calcule les déductions de stock pour `portions` portions d'une fiche
function calculerDeductions(stocks, fiche, portions) {
  const deductions = [];
  for (const ing of fiche.ingredients) {
    const s = trouverStock(stocks, ing.nom);
    if (!s) continue;
    const q = parseQuantite(ing.qte);
    if (!q || q.u !== (s.unite === "pièce" ? "pièce" : s.unite)) continue;
    const parPortion = q.v / fiche.portions;
    deductions.push({ stockId: s.id, produit: s.produit, qte: +(parPortion * portions).toFixed(3), unite: s.unite });
  }
  return deductions;
}

function Stocks({ stocks, setStocks }) {
  const [ajustements, setAjustements] = useState({});
  const [pole, setPole] = useState("Tout");
  const poles = ["Tout", "Cuisine", "Salle", "Bar"];
  const visibles = pole === "Tout" ? stocks : stocks.filter(s => s.pole === pole);
  const valeurTotale = stocks.reduce((s, p) => s + p.qte * p.pu, 0);
  const ruptures = stocks.filter(p => p.qte <= p.seuil);

  const ajuster = (id, sens) => {
    const delta = parseFloat(String(ajustements[id] || "1").replace(",", "."));
    if (isNaN(delta)) return;
    setStocks(stocks.map(p => p.id === id ? { ...p, qte: Math.max(0, +(p.qte + sens * delta).toFixed(2)) } : p));
  };

  return (
    <div>
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 16 }}>
        <Stat label="Valeur du stock" valeur={`${valeurTotale.toFixed(0)} €`} sous="Valorisation au PU d'achat" />
        <Stat label="Références" valeur={stocks.length} sous="Cuisine · Salle · Bar" />
        <Stat label="Alertes rupture" valeur={ruptures.length} sous={ruptures.length ? "Commande à passer" : "Stocks sains"} accent={ruptures.length ? C.rouille : C.vert} />
      </div>
      {ruptures.length > 0 && (
        <Carte style={{ marginBottom: 14, background: C.cremeFonce, borderLeft: `4px solid ${C.rouille}` }}>
          <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.rouille, marginBottom: 4 }}>⚠ Sous le seuil : {ruptures.map(r => r.produit).join(", ")}</div>
          <div style={{ fontSize: 12.5, color: C.brun, fontFamily: "Inter, system-ui, sans-serif" }}>Suggestion : grouper la commande chez vos fournisseurs habituels (voir module Fournisseurs).</div>
        </Carte>
      )}
      <div style={{ display: "flex", gap: 6, marginBottom: 12, flexWrap: "wrap" }}>
        {poles.map(p => (
          <button key={p} onClick={() => setPole(p)} style={{
            background: pole === p ? C.brun : C.blanc, color: pole === p ? C.creme : C.brun,
            border: `1.5px solid ${pole === p ? C.brun : C.orClair}`, borderRadius: 16,
            padding: "6px 14px", fontSize: 12, fontWeight: 700, cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
          }}>{p} {p !== "Tout" && `(${stocks.filter(s => s.pole === p).length})`}</button>
        ))}
      </div>
      {visibles.map(p => {
        const alerte = p.qte <= p.seuil;
        return (
          <Carte key={p.id} style={{ marginBottom: 8, borderLeft: `4px solid ${alerte ? C.rouille : C.vert}`, padding: 12 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
              <div style={{ minWidth: 180 }}>
                <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun, fontSize: 14 }}>{p.produit}</div>
                <div style={{ fontSize: 11.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>{p.pole} · {p.zone} · Seuil {p.seuil} {p.unite} · {p.pu.toFixed(2)} €/{p.unite}</div>
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 18, fontWeight: 700, color: alerte ? C.rouille : C.brun, minWidth: 70, textAlign: "right" }}>{p.qte} {p.unite}</span>
                <input value={ajustements[p.id] || ""} onChange={e => setAjustements({ ...ajustements, [p.id]: e.target.value })} placeholder="1"
                  style={{ width: 46, padding: "6px 6px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, background: C.blanc, color: C.brun, textAlign: "center" }} />
                <button onClick={() => ajuster(p.id, -1)} style={{ ...miniBtn, borderColor: C.rouille, color: C.rouille }}>−</button>
                <button onClick={() => ajuster(p.id, +1)} style={{ ...miniBtn, borderColor: C.vert, color: C.vert }}>+</button>
              </div>
            </div>
          </Carte>
        );
      })}
      <div style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 8 }}>Saisis une quantité puis − (sortie/consommation) ou + (réception). Les alertes se déclenchent automatiquement sous le seuil.</div>
    </div>
  );
}

// ============ EXTENSION : PLANNING ÉQUIPE ============
const EQUIPE = [
  { id: 1, nom: "Marie-Line", poste: "Chef de cuisine", tauxH: 16.5 },
  { id: 2, nom: "Kévin", poste: "Second", tauxH: 13.8 },
  { id: 3, nom: "Sandra", poste: "Cheffe de rang", tauxH: 12.5 },
  { id: 4, nom: "Loïc", poste: "Plonge / commis", tauxH: 11.9 },
];
const JOURS = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];
const SERVICES = ["Repos", "Midi", "Soir", "Doublon"];
const HEURES_SERVICE = { Repos: 0, Midi: 5, Soir: 5, Doublon: 9 };
const COULEURS_SERVICE = { Repos: "#D8CFBC", Midi: "#C8962A", Soir: "#6B5440", Doublon: "#8B3A1E" };

function planningInitial() {
  const init = {};
  EQUIPE.forEach(e => { init[e.id] = JOURS.map((_, j) => (j === 0 ? "Repos" : j >= 5 ? "Doublon" : "Soir")); });
  init[1] = ["Repos", "Doublon", "Doublon", "Doublon", "Doublon", "Doublon", "Midi"];
  return init;
}

function Planning({ planning, setPlanning }) {
  const [caPrev, setCaPrev] = useState("9500");

  const cycler = (empId, jour) => {
    const next = { ...planning };
    const actuel = SERVICES.indexOf(planning[empId][jour]);
    next[empId] = [...planning[empId]];
    next[empId][jour] = SERVICES[(actuel + 1) % SERVICES.length];
    setPlanning(next);
  };

  const heuresEmp = (e) => planning[e.id].reduce((s, srv) => s + HEURES_SERVICE[srv], 0);
  const coutTotal = EQUIPE.reduce((s, e) => s + heuresEmp(e) * e.tauxH * 1.42, 0); // charges patronales ~42%
  const ca = parseFloat(caPrev.replace(",", ".")) || 0;
  const ratio = ca > 0 ? (coutTotal / ca * 100) : 0;

  return (
    <div>
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 16 }}>
        <Stat label="Masse salariale semaine" valeur={`${coutTotal.toFixed(0)} €`} sous="Charges patronales incluses (≈42%)" />
        <Carte style={{ flex: 1, minWidth: 150, borderTop: `3px solid ${C.or}` }}>
          <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: 1, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>CA prévisionnel semaine</div>
          <input value={caPrev} onChange={e => setCaPrev(e.target.value)}
            style={{ width: "90%", marginTop: 6, padding: "7px 8px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Fraunces, Georgia, serif", fontSize: 18, fontWeight: 700, background: C.blanc, color: C.brun }} />
        </Carte>
        <Stat label="Ratio personnel / CA" valeur={`${ratio.toFixed(1)}%`} sous={ratio <= 35 ? "Bon (cible ≤ 35%)" : "Trop élevé (cible ≤ 35%)"} accent={ratio <= 35 ? C.vert : C.rouille} />
      </div>
      <Carte style={{ overflowX: "auto", padding: 12 }}>
        <table style={{ borderCollapse: "collapse", width: "100%", fontFamily: "Inter, system-ui, sans-serif", fontSize: 12 }}>
          <thead>
            <tr>
              <th style={{ textAlign: "left", padding: 8, color: C.brunMoyen, fontSize: 11, textTransform: "uppercase" }}>Équipe</th>
              {JOURS.map(j => <th key={j} style={{ padding: 8, color: C.brunMoyen, fontSize: 11 }}>{j}</th>)}
              <th style={{ padding: 8, color: C.brunMoyen, fontSize: 11 }}>H</th>
            </tr>
          </thead>
          <tbody>
            {EQUIPE.map(e => (
              <tr key={e.id} style={{ borderTop: `1px dashed ${C.orClair}` }}>
                <td style={{ padding: 8 }}>
                  <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun }}>{e.nom}</div>
                  <div style={{ fontSize: 10.5, color: C.brunMoyen }}>{e.poste}</div>
                </td>
                {JOURS.map((_, j) => (
                  <td key={j} style={{ padding: 4, textAlign: "center" }}>
                    <button onClick={() => cycler(e.id, j)} style={{
                      background: COULEURS_SERVICE[planning[e.id][j]], color: planning[e.id][j] === "Repos" ? C.brunMoyen : C.blanc,
                      border: "none", borderRadius: 6, padding: "6px 4px", fontSize: 10.5, fontWeight: 700, cursor: "pointer", width: 58, fontFamily: "Inter, system-ui, sans-serif",
                    }}>{planning[e.id][j]}</button>
                  </td>
                ))}
                <td style={{ padding: 8, textAlign: "center", fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: heuresEmp(e) > 39 ? C.rouille : C.brun }}>{heuresEmp(e)}</td>
              </tr>
            ))}
          </tbody>
        </table>
        <div style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 8 }}>Clique sur une case pour faire tourner : Repos → Midi → Soir → Doublon. Heures &gt; 39h/sem affichées en rouge (heures sup à anticiper).</div>
      </Carte>
    </div>
  );
}

// ============ EXTENSION : MENUS & CARTE DU JOUR ============
function Menus({ selection, setSelection, titreMenu, setTitreMenu, prixMenu, setPrixMenu, carteExclus, setCarteExclus, majFiches }) {
  const [prixEdit, setPrixEdit] = useState({});
  // Prix conseillé : food cost cible 30% arrondi au 0,50 € supérieur
  const prixConseille = (f) => Math.ceil((f.coutPortion / 0.30) * 2) / 2;
  const changerPrix = (f, val) => {
    setPrixEdit({ ...prixEdit, [f.id]: val });
    const v = parseFloat(String(val).replace(",", "."));
    if (!isNaN(v) && v > 0) { f.prixVente = v; majFiches(); }
  };
  const [vol, setVol] = useState("carte");
  const ordre = { "Entrée": 0, "Plat": 1, "Accompagnement": 2, "Dessert": 3 };

  // ----- Onglet MENU DU JOUR -----
  const basculer = (id) => setSelection(selection.includes(id) ? selection.filter(s => s !== id) : [...selection, id]);
  const plats = FICHES.filter(f => selection.includes(f.id));
  const coutMenu = plats.reduce((s, f) => s + f.coutPortion, 0);
  const prix = parseFloat(prixMenu.replace(",", ".")) || 0;
  const fcMenu = prix > 0 ? (coutMenu / prix * 100) : 0;
  const platsTries = [...plats].sort((a, b) => ordre[a.categorie] - ordre[b.categorie]);

  // ----- Onglet CARTE -----
  const surCarte = FICHES.filter(f => !carteExclus.includes(f.id));
  const basculerCarte = (id) => setCarteExclus(carteExclus.includes(id) ? carteExclus.filter(x => x !== id) : [...carteExclus, id]);

  return (
    <div>
      {/* Bascule Carte / Menu du jour */}
      <div style={{ display: "flex", gap: 8, marginBottom: 14 }}>
        {[["carte", "📜 Carte"], ["jour", "☀ Menu du jour"]].map(([id, label]) => (
          <button key={id} onClick={() => setVol(id)} style={{
            background: vol === id ? C.brun : C.blanc, color: vol === id ? C.creme : C.brun,
            border: `1.5px solid ${vol === id ? C.brun : C.orClair}`, borderRadius: 8,
            padding: "9px 18px", fontSize: 13, fontWeight: 700, cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
          }}>{label}</button>
        ))}
      </div>

      {vol === "carte" && (
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1.1fr", gap: 14 }}>
          <Carte>
            <h3 style={h3Style}>Plats à la carte ({surCarte.length}/{FICHES.length})</h3>
            <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginBottom: 8 }}>
              Décoche un plat pour le retirer de la carte. Modifie le prix directement — le prix conseillé (food cost 30%) sert de repère, touche-le pour l'appliquer. Tout se répercute partout : caisse, salle, food cost.
            </div>
            <div style={{ maxHeight: 420, overflowY: "auto" }}>
              {["Entrée", "Plat", "Accompagnement", "Dessert"].map(cat => {
                const items = FICHES.filter(f => f.categorie === cat);
                if (!items.length) return null;
                return (
                  <div key={cat}>
                    <div style={{ fontSize: 10, letterSpacing: 2, color: C.or, textTransform: "uppercase", fontFamily: "Inter, system-ui, sans-serif", margin: "10px 0 4px", fontWeight: 700 }}>{cat}s</div>
                    {items.map(f => {
                      const conseil = prixConseille(f);
                      const ecart = f.prixVente - conseil;
                      return (
                        <div key={f.id} style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 4px", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, opacity: carteExclus.includes(f.id) ? 0.5 : 1 }}>
                          <input type="checkbox" checked={!carteExclus.includes(f.id)} onChange={() => basculerCarte(f.id)} style={{ accentColor: C.or, cursor: "pointer" }} />
                          <span style={{ flex: 1, minWidth: 0 }}>
                            {f.nom}
                            <span onClick={() => changerPrix(f, String(conseil))} title="Appliquer le prix conseillé" style={{ display: "block", fontSize: 10, color: Math.abs(ecart) < 0.26 ? C.vert : ecart < 0 ? C.rouille : C.brunMoyen, cursor: "pointer" }}>
                              conseillé : {conseil.toFixed(2)} € (FC 30%){ecart < -0.25 ? " · sous-vendu ⚠" : ""}
                            </span>
                          </span>
                          <div style={{ display: "flex", alignItems: "center", gap: 3 }}>
                            <input value={prixEdit[f.id] ?? f.prixVente.toFixed(2)} onChange={e => changerPrix(f, e.target.value)}
                              style={{ width: 58, padding: "5px 6px", border: `1.5px solid ${C.orClair}`, borderRadius: 8, fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, fontWeight: 700, textAlign: "right", background: C.blanc, color: C.or }} />
                            <span style={{ fontSize: 11, color: C.brunMoyen }}>€</span>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                );
              })}
            </div>
          </Carte>
          {/* Aperçu carte du restaurant */}
          <Carte style={{ background: C.blanc, border: `2px solid ${C.or}`, padding: 26, position: "relative" }}>
            <div style={{ position: "absolute", top: 8, left: 8, right: 8, bottom: 8, border: `1px solid ${C.orClair}`, borderRadius: 4, pointerEvents: "none" }} />
            <div style={{ textAlign: "center" }}>
              <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 13, letterSpacing: 3, color: C.or, textTransform: "uppercase" }}>La Kaz Créole</div>
              <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 26, fontWeight: 700, color: C.brun, margin: "6px 0" }}>La Carte</div>
              <div style={{ width: 60, height: 2, background: C.or, margin: "10px auto" }} />
            </div>
            <div style={{ marginTop: 16, maxHeight: 420, overflowY: "auto" }}>
              {["Entrée", "Plat", "Accompagnement", "Dessert"].map(cat => {
                const items = surCarte.filter(p => p.categorie === cat).sort((a, b) => a.prixVente - b.prixVente);
                if (!items.length) return null;
                return (
                  <div key={cat} style={{ marginBottom: 16 }}>
                    <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 10, letterSpacing: 2, color: C.or, textTransform: "uppercase", marginBottom: 8, textAlign: "center" }}>— {cat}s —</div>
                    {items.map(p => (
                      <div key={p.id} style={{ marginBottom: 11 }}>
                        <div style={{ display: "flex", alignItems: "baseline", gap: 6 }}>
                          <span style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 15, color: C.brun }}>{p.nom}</span>
                          <span style={{ flex: 1, borderBottom: `1px dotted ${C.orClair}` }} />
                          <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.rouille, fontSize: 14 }}>{p.prixVente.toFixed(2).replace(".", ",")} €</span>
                        </div>
                        {p.description && <div style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", fontStyle: "italic", marginTop: 1, lineHeight: 1.4 }}>{p.description}</div>}
                        {p.allergenes[0] !== "Aucun allergène majeur" &&
                          <div style={{ fontSize: 9.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>Allergènes : {p.allergenes.join(", ")}</div>}
                      </div>
                    ))}
                  </div>
                );
              })}
            </div>
            <div style={{ textAlign: "center", marginTop: 8, fontFamily: "Fraunces, Georgia, serif", fontSize: 10, color: C.brunMoyen, fontStyle: "italic" }}>Fait maison · Produits pays · Prix nets TTC</div>
          </Carte>
        </div>
      )}

      {vol === "jour" && (
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1.1fr", gap: 14 }}>
          <div>
            <Carte style={{ marginBottom: 12 }}>
              <h3 style={h3Style}>Composer le menu</h3>
              <div style={{ display: "flex", gap: 8, marginBottom: 10 }}>
                <input value={titreMenu} onChange={e => setTitreMenu(e.target.value)}
                  style={{ flex: 1, padding: "8px 10px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, background: C.blanc, color: C.brun }} />
                <input value={prixMenu} onChange={e => setPrixMenu(e.target.value)} placeholder="Prix €"
                  style={{ width: 70, padding: "8px 10px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, background: C.blanc, color: C.brun, textAlign: "center" }} />
              </div>
              <div style={{ maxHeight: 320, overflowY: "auto" }}>
                {FICHES.map(f => (
                  <label key={f.id} style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 4px", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, cursor: "pointer" }}>
                    <input type="checkbox" checked={selection.includes(f.id)} onChange={() => basculer(f.id)} style={{ accentColor: C.or }} />
                    <span style={{ flex: 1 }}>{f.nom}</span>
                    <span style={{ fontSize: 10.5, color: C.brunMoyen }}>{f.categorie} · {f.coutPortion.toFixed(2)} €</span>
                  </label>
                ))}
              </div>
            </Carte>
            <Carte style={{ background: C.cremeFonce }}>
              <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, color: C.brun, lineHeight: 1.8 }}>
                <div style={{ display: "flex", justifyContent: "space-between" }}><span>Coût matière du menu</span><b>{coutMenu.toFixed(2)} €</b></div>
                <div style={{ display: "flex", justifyContent: "space-between" }}><span>Prix de vente</span><b>{prix.toFixed(2)} €</b></div>
                <div style={{ display: "flex", justifyContent: "space-between", color: fcMenu <= 30 ? C.vert : C.rouille }}>
                  <span>Food cost du menu</span><b>{fcMenu.toFixed(1)}% {fcMenu <= 30 ? "✓" : "⚠ > 30%"}</b>
                </div>
              </div>
            </Carte>
          </div>
          {/* Aperçu menu du jour imprimable */}
          <Carte style={{ background: C.blanc, border: `2px solid ${C.or}`, padding: 26, position: "relative" }}>
            <div style={{ position: "absolute", top: 8, left: 8, right: 8, bottom: 8, border: `1px solid ${C.orClair}`, borderRadius: 4, pointerEvents: "none" }} />
            <div style={{ textAlign: "center" }}>
              <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 13, letterSpacing: 3, color: C.or, textTransform: "uppercase" }}>La Kaz Créole</div>
              <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 26, fontWeight: 700, color: C.brun, margin: "6px 0" }}>{titreMenu}</div>
              <div style={{ width: 60, height: 2, background: C.or, margin: "10px auto" }} />
              <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 20, color: C.rouille, fontWeight: 700 }}>{prix.toFixed(2).replace(".", ",")} €</div>
            </div>
            <div style={{ marginTop: 18 }}>
              {["Entrée", "Plat", "Accompagnement", "Dessert"].map(cat => {
                const items = platsTries.filter(p => p.categorie === cat);
                if (!items.length) return null;
                return (
                  <div key={cat} style={{ marginBottom: 14, textAlign: "center" }}>
                    <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 10, letterSpacing: 2, color: C.or, textTransform: "uppercase", marginBottom: 6 }}>— {cat} —</div>
                    {items.map(p => (
                      <div key={p.id} style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 16, color: C.brun, marginBottom: 7 }}>
                        {p.nom}
                        {p.description && <div style={{ fontSize: 11, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", fontStyle: "italic", lineHeight: 1.4 }}>{p.description}</div>}
                        <div style={{ fontSize: 9.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>Allergènes : {p.allergenes.join(", ")}</div>
                      </div>
                    ))}
                  </div>
                );
              })}
              {!plats.length && <div style={{ textAlign: "center", color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, fontStyle: "italic" }}>Coche des plats à gauche pour composer ton menu</div>}
            </div>
            <div style={{ textAlign: "center", marginTop: 10, fontFamily: "Fraunces, Georgia, serif", fontSize: 10, color: C.brunMoyen, fontStyle: "italic" }}>Fait maison · Produits pays · Prix nets TTC</div>
          </Carte>
        </div>
      )}
    </div>
  );
}

// ============ EXTENSION : SERVICE EN SALLE (plan de salle + écran cuisine) ============
const TABLES_INIT = [
  { num: 1, places: 2, x: 8, y: 12, statut: "libre" },
  { num: 2, places: 2, x: 30, y: 12, statut: "libre" },
  { num: 3, places: 4, x: 55, y: 10, statut: "occupee" },
  { num: 4, places: 4, x: 80, y: 12, statut: "libre" },
  { num: 5, places: 6, x: 12, y: 48, statut: "libre" },
  { num: 6, places: 4, x: 42, y: 50, statut: "occupee" },
  { num: 7, places: 2, x: 70, y: 48, statut: "libre" },
  { num: 8, places: 8, x: 25, y: 80, statut: "libre" },
  { num: 9, places: 4, x: 60, y: 82, statut: "libre" },
  { num: 10, places: 2, x: 85, y: 80, statut: "libre" },
];

const STATUT_COMMANDE = { en_attente: "Envoyée", en_cuisine: "En cuisine", prete: "Prête", servie: "Servie" };
const COULEUR_COMMANDE = { en_attente: "#C8962A", en_cuisine: "#8B3A1E", prete: "#4A6741", servie: "#6B5440" };

function Service({ tables, setTables, commandes, setCommandes, mep, setMep, enregistrerVente, reservations }) {
  const dispoDe = (ficheId) => mep[ficheId]?.valide ? mep[ficheId].dispo : null;
  const resaDeTable = (num) => (reservations || []).find(r => r.table === num && r.statut === "a_venir");
  const [vue, setVue] = useState("salle");
  const [tableSel, setTableSel] = useState(null);
  const [panier, setPanier] = useState({});
  const [horloge, setHorloge] = useState(Date.now());
  useEffect(() => { const t = setInterval(() => setHorloge(Date.now()), 30000); return () => clearInterval(t); }, []);

  const cmdsTable = (num) => commandes.filter(c => c.table === num && c.statut !== "servie");
  const totalTable = (num) => commandes.filter(c => c.table === num && c.statut !== "servie")
    .reduce((s, c) => s + c.items.reduce((t, i) => t + (FICHES.find(f => f.id === i.ficheId)?.prixVente || 0) * i.qte, 0), 0);
  const pretesPourSalle = commandes.filter(c => c.statut === "prete").length;
  const caService = commandes.filter(c => c.statut === "servie")
    .reduce((s, c) => s + c.items.reduce((t, i) => t + (FICHES.find(f => f.id === i.ficheId)?.prixVente || 0) * i.qte, 0), 0);

  const couleurTable = (t) => {
    if (cmdsTable(t.num).some(c => c.statut === "prete")) return C.vert;
    if (t.statut === "occupee") return C.rouille;
    return C.or;
  };

  const ajouterPanier = (ficheId) => setPanier({ ...panier, [ficheId]: (panier[ficheId] || 0) + 1 });
  const retirerPanier = (ficheId) => {
    const nv = { ...panier };
    if (nv[ficheId] > 1) nv[ficheId]--; else delete nv[ficheId];
    setPanier(nv);
  };

  const envoyerEnCuisine = () => {
    const items = Object.entries(panier).map(([id, qte]) => ({ ficheId: Number(id), qte }));
    if (!items.length || !tableSel) return;
    setCommandes([...commandes, {
      id: Date.now(), table: tableSel, items, statut: "en_attente",
      heure: new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }), ts: Date.now(),
    }]);
    setTables(tables.map(t => t.num === tableSel ? { ...t, statut: "occupee" } : t));
    // Décrémenter les disponibilités de la mise en place
    setMep(prev => {
      const nv = { ...prev };
      for (const i of items) {
        if (nv[i.ficheId]?.valide) nv[i.ficheId] = { ...nv[i.ficheId], dispo: Math.max(0, nv[i.ficheId].dispo - i.qte) };
      }
      return nv;
    });
    setPanier({});
  };

  const avancerCommande = (id) => {
    const suite = { en_attente: "en_cuisine", en_cuisine: "prete", prete: "servie" };
    setCommandes(commandes.map(c => c.id === id ? { ...c, statut: suite[c.statut] || c.statut } : c));
  };

  const encaisser = (num) => {
    // Enregistrer l'addition en caisse (origine Table)
    const aPayer = commandes.filter(c => c.table === num && c.statut !== "servie");
    const items = aPayer.flatMap(c => c.items.map(i => ({ ficheId: i.ficheId, qte: i.qte, pu: FICHES.find(f => f.id === i.ficheId)?.prixVente || 0 })));
    const total = items.reduce((s, i) => s + i.pu * i.qte, 0);
    if (items.length) enregistrerVente({ items, total, mode: "CB", origine: `Table ${num}` });
    setCommandes(commandes.map(c => c.table === num ? { ...c, statut: "servie" } : c));
    setTables(tables.map(t => t.num === num ? { ...t, statut: "libre" } : t));
    setTableSel(null);
  };

  const minutesDepuis = (ts) => Math.max(0, Math.round((horloge - ts) / 60000));
  const ticketsCuisine = commandes.filter(c => c.statut === "en_attente" || c.statut === "en_cuisine").sort((a, b) => a.ts - b.ts);

  return (
    <div>
      {/* Bascule Salle / Cuisine */}
      <div style={{ display: "flex", gap: 8, marginBottom: 14, alignItems: "center", flexWrap: "wrap" }}>
        {[["salle", "🍽 Interface salle"], ["cuisine", `🔥 Écran cuisine${ticketsCuisine.length ? ` (${ticketsCuisine.length})` : ""}`]].map(([id, label]) => (
          <button key={id} onClick={() => setVue(id)} style={{
            background: vue === id ? C.brun : C.blanc, color: vue === id ? C.creme : C.brun,
            border: `1.5px solid ${vue === id ? C.brun : C.orClair}`, borderRadius: 8,
            padding: "9px 18px", fontSize: 13, fontWeight: 700, cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
          }}>{label}</button>
        ))}
        <div style={{ marginLeft: "auto", fontFamily: "Fraunces, Georgia, serif", fontSize: 13, color: C.brun }}>
          CA du service : <b style={{ color: C.or }}>{caService.toFixed(2)} €</b>
          {pretesPourSalle > 0 && <span style={{ marginLeft: 12, color: C.vert, fontWeight: 700 }}>● {pretesPourSalle} plat(s) à servir !</span>}
        </div>
      </div>

      {vue === "salle" && (
        <div style={{ display: "grid", gridTemplateColumns: tableSel ? "1.3fr 1fr" : "1fr", gap: 14 }}>
          {/* Plan de salle */}
          <Carte style={{ position: "relative", height: 420, background: `repeating-linear-gradient(0deg, ${C.blanc}, ${C.blanc} 38px, ${C.creme} 38px, ${C.creme} 40px)`, overflow: "hidden" }}>
            <div style={{ position: "absolute", top: 8, left: 12, fontSize: 10, letterSpacing: 2, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", textTransform: "uppercase" }}>Terrasse ↑ · Bar →</div>
            {tables.map(t => {
              const aServir = cmdsTable(t.num).some(c => c.statut === "prete");
              return (
                <button key={t.num} onClick={() => { setTableSel(t.num); setPanier({}); }} style={{
                  position: "absolute", left: `${t.x}%`, top: `${t.y}%`,
                  width: t.places >= 6 ? 84 : t.places >= 4 ? 68 : 54,
                  height: t.places >= 6 ? 84 : t.places >= 4 ? 68 : 54,
                  borderRadius: t.places === 2 ? "50%" : 12,
                  background: tableSel === t.num ? C.brun : C.blanc,
                  border: `3px solid ${couleurTable(t)}`,
                  color: tableSel === t.num ? C.creme : C.brun,
                  cursor: "pointer", fontFamily: "Fraunces, Georgia, serif", fontWeight: 700,
                  boxShadow: aServir ? `0 0 0 6px rgba(74,103,65,0.25)` : "0 2px 6px rgba(59,42,26,0.15)",
                  display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
                }}>
                  <span style={{ fontSize: 17 }}>{t.num}</span>
                  <span style={{ fontSize: 9.5, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 400 }}>{t.places} cv</span>
                  {resaDeTable(t.num) && (
                    <span title={`Réservée : ${resaDeTable(t.num).nom} à ${resaDeTable(t.num).heure}`} style={{
                      position: "absolute", top: -7, left: -7, width: 22, height: 22, borderRadius: 11,
                      background: C.brun, color: C.orClair, fontSize: 11, display: "flex", alignItems: "center",
                      justifyContent: "center", border: `2px solid ${C.creme}`,
                    }}>✆</span>
                  )}
                </button>
              );
            })}
            <div style={{ position: "absolute", bottom: 8, left: 12, display: "flex", gap: 12, fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>
              <span><span style={{ color: C.or }}>■</span> Libre</span>
              <span><span style={{ color: C.rouille }}>■</span> Occupée</span>
              <span><span style={{ color: C.vert }}>■</span> Plat prêt à servir</span>
              <span><span style={{ color: C.brun }}>✆</span> Réservée</span>
            </div>
          </Carte>

          {/* Panneau commande de la table sélectionnée */}
          {tableSel && (() => {
            const t = tables.find(x => x.num === tableSel);
            const enCours = cmdsTable(tableSel);
            const nbPanier = Object.values(panier).reduce((s, n) => s + n, 0);
            return (
              <Carte style={{ display: "flex", flexDirection: "column", maxHeight: 420, overflow: "hidden" }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
                  <h3 style={{ ...h3Style, margin: 0, border: "none" }}>Table {t.num} · {t.places} couverts</h3>
                  <button onClick={() => setTableSel(null)} style={{ ...btnStyle(C.brunMoyen), marginTop: 0, padding: "4px 10px" }}>✕</button>
                </div>
                <div style={{ flex: 1, overflowY: "auto" }}>
                  {enCours.length > 0 && (
                    <div style={{ marginBottom: 10 }}>
                      {enCours.map(c => (
                        <div key={c.id} style={{ padding: 8, background: C.creme, borderRadius: 8, marginBottom: 6, border: `1px solid ${C.orClair}` }}>
                          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                            <span style={{ fontSize: 11, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>Cmd {c.heure}</span>
                            <Badge bg={COULEUR_COMMANDE[c.statut]}>{STATUT_COMMANDE[c.statut]}</Badge>
                          </div>
                          {c.items.map((i, k) => {
                            const f = FICHES.find(x => x.id === i.ficheId);
                            return <div key={k} style={{ fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, marginTop: 3 }}>{i.qte}× {f?.nom}</div>;
                          })}
                          {c.statut === "prete" && <button onClick={() => avancerCommande(c.id)} style={{ ...btnStyle(C.vert), width: "100%" }}>✓ Servie en salle</button>}
                        </div>
                      ))}
                    </div>
                  )}
                  <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: 1, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", margin: "6px 0" }}>Nouvelle commande</div>
                  {FICHES.map(f => {
                    const dispo = dispoDe(f.id);
                    const restant = dispo === null ? null : dispo - (panier[f.id] || 0);
                    const epuise = dispo !== null && restant <= 0;
                    return (
                      <div key={f.id} style={{ display: "flex", alignItems: "center", gap: 6, padding: "5px 0", borderBottom: `1px dashed ${C.orClair}`, opacity: epuise && !panier[f.id] ? 0.45 : 1 }}>
                        <span style={{ flex: 1, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                          {f.nom} <span style={{ color: C.brunMoyen, fontSize: 10.5 }}>{f.prixVente.toFixed(2)} €</span>
                        </span>
                        {dispo !== null ? (
                          <span style={{
                            fontSize: 10, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, padding: "2px 7px", borderRadius: 10,
                            background: restant <= 0 ? "#F3DCD4" : restant <= 3 ? "#F5EAD3" : "#E3EBDD",
                            color: restant <= 0 ? C.rouille : restant <= 3 ? C.or : C.vert,
                          }}>{restant <= 0 ? "ÉPUISÉ" : `${restant} dispo`}</span>
                        ) : (
                          <span style={{ fontSize: 10, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontStyle: "italic" }}>pas de MEP</span>
                        )}
                        {panier[f.id] && <>
                          <button onClick={() => retirerPanier(f.id)} style={{ ...miniBtn, width: 22, height: 22, fontSize: 13, borderColor: C.rouille, color: C.rouille }}>−</button>
                          <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun, minWidth: 16, textAlign: "center", fontSize: 13 }}>{panier[f.id]}</span>
                        </>}
                        <button onClick={() => !epuise && ajouterPanier(f.id)} disabled={epuise}
                          style={{ ...miniBtn, width: 22, height: 22, fontSize: 13, opacity: epuise ? 0.35 : 1, cursor: epuise ? "default" : "pointer" }}>+</button>
                      </div>
                    );
                  })}
                </div>
                <div style={{ paddingTop: 10, display: "flex", gap: 8 }}>
                  <button onClick={envoyerEnCuisine} disabled={!nbPanier} style={{ ...btnStyle(C.or), flex: 1, marginTop: 0, opacity: nbPanier ? 1 : 0.45, padding: "10px" }}>
                    🔥 Envoyer en cuisine {nbPanier ? `(${nbPanier})` : ""}
                  </button>
                  {t.statut === "occupee" && (
                    <button onClick={() => encaisser(t.num)} style={{ ...btnStyle(C.vert), flex: 1, marginTop: 0, padding: "10px" }}>
                      💶 Encaisser {totalTable(t.num).toFixed(2)} €
                    </button>
                  )}
                </div>
              </Carte>
            );
          })()}
        </div>
      )}

      {vue === "cuisine" && (
        <div>
          {ticketsCuisine.length === 0 && (
            <Carte style={{ textAlign: "center", padding: 40, color: C.brunMoyen, fontFamily: "Fraunces, Georgia, serif", fontStyle: "italic" }}>
              Aucun ticket en attente — la cuisine respire. 🔥
            </Carte>
          )}
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 12 }}>
            {ticketsCuisine.map(c => {
              const attente = minutesDepuis(c.ts);
              const urgent = attente >= 15;
              return (
                <Carte key={c.id} style={{ borderTop: `5px solid ${urgent ? C.rouille : COULEUR_COMMANDE[c.statut]}`, padding: 0, overflow: "hidden" }}>
                  <div style={{ background: urgent ? C.rouille : C.brun, color: C.creme, padding: "8px 12px", display: "flex", justifyContent: "space-between", fontFamily: "Fraunces, Georgia, serif", fontWeight: 700 }}>
                    <span>Table {c.table}</span>
                    <span style={{ fontSize: 12, fontFamily: "Inter, system-ui, sans-serif" }}>{c.heure} · {attente} min {urgent && "⚠"}</span>
                  </div>
                  <div style={{ padding: 12 }}>
                    {c.items.map((i, k) => {
                      const f = FICHES.find(x => x.id === i.ficheId);
                      return (
                        <div key={k} style={{ padding: "6px 0", borderBottom: `1px dashed ${C.orClair}` }}>
                          <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 15, color: C.brun }}>{i.qte} × {f?.nom}</div>
                          {f?.allergenes?.length > 0 && f.allergenes[0] !== "Aucun allergène majeur" &&
                            <div style={{ fontSize: 10.5, color: C.rouille, fontFamily: "Inter, system-ui, sans-serif" }}>⚠ {f.allergenes.join(", ")}</div>}
                        </div>
                      );
                    })}
                    <button onClick={() => avancerCommande(c.id)} style={{
                      ...btnStyle(c.statut === "en_attente" ? C.rouille : C.vert), width: "100%", marginTop: 10, padding: "11px", fontSize: 13,
                    }}>
                      {c.statut === "en_attente" ? "👨‍🍳 Commencer" : "✓ Prête — appeler la salle"}
                    </button>
                  </div>
                </Carte>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}


// ============ MODULE NATIF : CAISSE (POS) ============
const MODES_PAIEMENT = ["CB", "Espèces", "Ticket resto", "Chèque"];
const OPERATEURS_PAIEMENT = [
  { id: "sumup", nom: "SumUp", icone: "▣", desc: "Terminal Air / Solo · 1,75%/transaction" },
  { id: "zettle", nom: "Zettle by PayPal", icone: "◈", desc: "Reader 2 · 1,75%/transaction" },
  { id: "stripe", nom: "Stripe Terminal", icone: "❘❘", desc: "BBPOS / S700 · 1,4% + 0,10€" },
  { id: "smileandpay", nom: "Smile&Pay", icone: "◡", desc: "Maxi Smile · sans abonnement" },
  { id: "yavin", nom: "Yavin", icone: "◆", desc: "Terminal Android · taux négociés" },
];

function Caisse({ ventes, enregistrerVente, mep, setMep, operateur, setOperateur }) {
  const [ticket, setTicket] = useState({});
  const [mode, setMode] = useState("CB");
  const [filtreCat, setFiltreCat] = useState("Tout");
  const [terminalEnCours, setTerminalEnCours] = useState(false);
  const [choixOperateur, setChoixOperateur] = useState(false);
  const opActif = OPERATEURS_PAIEMENT.find(o => o.id === operateur);
  const cats = ["Tout", "Entrée", "Plat", "Accompagnement", "Dessert"];

  const dispoDe = (id) => mep[id]?.valide ? mep[id].dispo : null;
  const lignes = Object.entries(ticket).map(([id, qte]) => ({ fiche: FICHES.find(f => f.id === Number(id)), qte })).filter(l => l.fiche);
  const total = lignes.reduce((s, l) => s + l.fiche.prixVente * l.qte, 0);

  const ajouter = (f) => {
    const dispo = dispoDe(f.id);
    const restant = dispo === null ? Infinity : dispo - (ticket[f.id] || 0);
    if (restant <= 0) return;
    setTicket({ ...ticket, [f.id]: (ticket[f.id] || 0) + 1 });
  };
  const retirer = (id) => {
    const nv = { ...ticket };
    if (nv[id] > 1) nv[id]--; else delete nv[id];
    setTicket(nv);
  };

  const finaliserVente = () => {
    enregistrerVente({
      items: lignes.map(l => ({ ficheId: l.fiche.id, qte: l.qte, pu: l.fiche.prixVente })),
      total, mode: `${mode} · ${opActif.nom}`, origine: "Comptoir",
    });
    setMep(prev => {
      const nv = { ...prev };
      for (const l of lignes) if (nv[l.fiche.id]?.valide) nv[l.fiche.id] = { ...nv[l.fiche.id], dispo: Math.max(0, nv[l.fiche.id].dispo - l.qte) };
      return nv;
    });
    setTicket({});
  };

  const [etapeTerminal, setEtapeTerminal] = useState(null); // "envoi" | "paiement" | null
  const encaisser = () => {
    if (!lignes.length || terminalEnCours) return;
    if (!opActif) return; // Option 1 : pas d'encaissement sans caisse certifiée connectée
    // 1. Push du panier vers la caisse certifiée de l'opérateur (API checkout)
    setTerminalEnCours(true);
    setEtapeTerminal("envoi");
    setTimeout(() => {
      // 2. Paiement sur le terminal (CB) ou validation dans la caisse opérateur (espèces/TR/chèque)
      setEtapeTerminal("paiement");
      setTimeout(() => {
        // 3. Webhook de confirmation de l'opérateur → copie de gestion dans Mizen Pro
        setTerminalEnCours(false);
        setEtapeTerminal(null);
        finaliserVente();
      }, mode === "CB" ? 1600 : 800);
    }, 700);
  };

  // ---- Z du jour ----
  const caJour = ventes.reduce((s, v) => s + v.total, 0);
  const parMode = MODES_PAIEMENT.map(m => ({ m, t: ventes.filter(v => v.mode === m).reduce((s, v) => s + v.total, 0) })).filter(x => x.t > 0);
  const ticketMoyen = ventes.length ? caJour / ventes.length : 0;

  const visibles = (filtreCat === "Tout" ? FICHES : FICHES.filter(f => f.categorie === filtreCat));

  return (
    <div>
      {/* Connexion opérateur de paiement */}
      <Carte style={{ marginBottom: 14, padding: 12, borderLeft: `4px solid ${opActif ? C.vert : C.or}` }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
          <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, color: C.brun }}>
            <b style={{ fontFamily: "Fraunces, Georgia, serif" }}>Terminal de paiement : </b>
            {opActif ? <span style={{ color: C.vert, fontWeight: 700 }}>● Caisse {opActif.nom} connectée</span>
              : <span style={{ color: C.rouille, fontWeight: 700 }}>⚠ Connecte ta caisse certifiée pour encaisser</span>}
          </div>
          <button onClick={() => setChoixOperateur(!choixOperateur)} style={{ ...btnStyle(opActif ? C.brunMoyen : C.or), marginTop: 0 }}>
            {opActif ? "Changer / déconnecter" : "🔌 Connecter un opérateur"}
          </button>
        </div>
        <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginTop: 6, lineHeight: 1.5 }}>
          Mizen Pro prépare le ticket et l'envoie à la caisse certifiée de ton opérateur, qui réalise l'enregistrement fiscal (art. 286 CGI).
          Mizen Pro conserve une copie de gestion pour tes stats, ta mise en place et tes commandes — aucune double saisie.
        </div>
        {choixOperateur && (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(170px, 1fr))", gap: 8, marginTop: 10 }}>
            {OPERATEURS_PAIEMENT.map(o => (
              <button key={o.id} onClick={() => { setOperateur(operateur === o.id ? null : o.id); setChoixOperateur(false); }} style={{
                background: operateur === o.id ? "#EDF2E9" : C.creme, border: `1.5px solid ${operateur === o.id ? C.vert : C.orClair}`,
                borderRadius: 10, padding: "10px 8px", cursor: "pointer", textAlign: "left",
              }}>
                <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 13.5, color: C.brun }}>{o.icone} {o.nom} {operateur === o.id && "✓"}</div>
                <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginTop: 3 }}>{o.desc}</div>
                <div style={{ fontSize: 10, fontFamily: "Inter, system-ui, sans-serif", color: operateur === o.id ? C.rouille : C.vert, fontWeight: 700, marginTop: 4 }}>
                  {operateur === o.id ? "Déconnecter" : "Connecter via API"}
                </div>
              </button>
            ))}
          </div>
        )}
      </Carte>

      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
        <Stat label="CA du jour" valeur={`${caJour.toFixed(2)} €`} sous={`${ventes.length} ticket(s)`} />
        <Stat label="Ticket moyen" valeur={`${ticketMoyen.toFixed(2)} €`} sous="Toutes origines" />
        {parMode.map(x => <Stat key={x.m} label={x.m} valeur={`${x.t.toFixed(2)} €`} accent={C.brunMoyen} />)}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 14 }}>
        {/* Grille de vente rapide */}
        <Carte>
          <div style={{ display: "flex", gap: 6, marginBottom: 10, flexWrap: "wrap" }}>
            {cats.map(c => (
              <button key={c} onClick={() => setFiltreCat(c)} style={{
                background: filtreCat === c ? C.brun : C.blanc, color: filtreCat === c ? C.creme : C.brun,
                border: `1.5px solid ${filtreCat === c ? C.brun : C.orClair}`, borderRadius: 14,
                padding: "5px 12px", fontSize: 11.5, fontWeight: 700, cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
              }}>{c}</button>
            ))}
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(118px, 1fr))", gap: 8, maxHeight: 380, overflowY: "auto" }}>
            {visibles.map(f => {
              const dispo = dispoDe(f.id);
              const restant = dispo === null ? null : dispo - (ticket[f.id] || 0);
              const epuise = restant !== null && restant <= 0;
              return (
                <button key={f.id} onClick={() => ajouter(f)} disabled={epuise} style={{
                  background: epuise ? C.cremeFonce : C.creme, border: `1.5px solid ${epuise ? C.orClair : C.or}`,
                  borderRadius: 10, padding: "10px 8px", cursor: epuise ? "default" : "pointer",
                  textAlign: "center", opacity: epuise ? 0.5 : 1, position: "relative",
                }}>
                  {ticket[f.id] && <span style={{ position: "absolute", top: -7, right: -6, minWidth: 22, height: 22, borderRadius: 11, background: C.or, color: C.blanc, fontSize: 11.5, fontWeight: 700, fontFamily: "Inter, system-ui, sans-serif", display: "flex", alignItems: "center", justifyContent: "center", border: `2px solid ${C.blanc}` }}>{ticket[f.id]}</span>}
                  <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 11.5, fontWeight: 700, color: C.brun, lineHeight: 1.25, minHeight: 28 }}>{f.nom}</div>
                  <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.rouille, fontSize: 13, marginTop: 4 }}>{f.prixVente.toFixed(2)} €</div>
                  <div style={{ fontSize: 9.5, fontFamily: "Inter, system-ui, sans-serif", color: epuise ? C.rouille : restant !== null ? C.vert : C.brunMoyen, fontWeight: 700, marginTop: 2 }}>
                    {restant === null ? "pas de MEP" : epuise ? "ÉPUISÉ" : `${restant} dispo`}
                  </div>
                </button>
              );
            })}
          </div>
        </Carte>

        {/* Ticket en cours */}
        <Carte style={{ display: "flex", flexDirection: "column" }}>
          <h3 style={h3Style}>Ticket en cours</h3>
          <div style={{ flex: 1, minHeight: 160, maxHeight: 240, overflowY: "auto" }}>
            {!lignes.length && <div style={{ color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", fontSize: 12.5, fontStyle: "italic", textAlign: "center", marginTop: 30 }}>Touche un plat pour l'ajouter</div>}
            {lignes.map(l => (
              <div key={l.fiche.id} style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
                <button onClick={() => retirer(l.fiche.id)} style={{ ...miniBtn, width: 22, height: 22, fontSize: 13, borderColor: C.rouille, color: C.rouille }}>−</button>
                <span style={{ flex: 1 }}>{l.qte} × {l.fiche.nom}</span>
                <b>{(l.fiche.prixVente * l.qte).toFixed(2)} €</b>
              </div>
            ))}
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 19, color: C.brun, padding: "10px 0", borderTop: `2px solid ${C.or}` }}>
            <span>TOTAL</span><span style={{ color: C.rouille }}>{total.toFixed(2)} €</span>
          </div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 10 }}>
            {MODES_PAIEMENT.map(m => (
              <button key={m} onClick={() => setMode(m)} style={{
                flex: 1, minWidth: 70, background: mode === m ? C.or : C.blanc, color: mode === m ? C.blanc : C.brun,
                border: `1.5px solid ${mode === m ? C.or : C.orClair}`, borderRadius: 8,
                padding: "8px 4px", fontSize: 11.5, fontWeight: 700, cursor: "pointer", fontFamily: "Inter, system-ui, sans-serif",
              }}>{m}</button>
            ))}
          </div>
          <button onClick={encaisser} disabled={!lignes.length || terminalEnCours || !opActif} style={{ ...btnStyle(terminalEnCours ? C.brunMoyen : opActif ? C.vert : C.rouille), width: "100%", padding: "13px", fontSize: 14, marginTop: 0, opacity: lignes.length && opActif ? 1 : 0.5 }}>
            {!opActif ? "🔌 Connecte un opérateur pour encaisser"
              : etapeTerminal === "envoi" ? `📤 Envoi du ticket à la caisse ${opActif.nom}…`
              : etapeTerminal === "paiement" ? (mode === "CB" ? `⏳ Paiement sur le terminal ${opActif.nom}… présentez la carte` : `⏳ Validation ${mode} dans la caisse ${opActif.nom}…`)
              : `💶 Encaisser ${total > 0 ? total.toFixed(2) + " €" : ""} (${mode} · ${opActif.nom})`}
          </button>
        </Carte>
      </div>

      {/* Journal des ventes */}
      {ventes.length > 0 && (
        <Carte style={{ marginTop: 14 }}>
          <h3 style={h3Style}>Journal du jour</h3>
          <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginBottom: 8 }}>
            Copie de gestion — l'enregistrement fiscal de référence (Z de caisse) est tenu par la caisse certifiée de l'opérateur. Réf. ✓ = confirmation webhook.
          </div>
          {[...ventes].reverse().slice(0, 12).map(v => (
            <div key={v.id} style={{ display: "flex", gap: 10, alignItems: "center", padding: "5px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
              <span style={{ color: C.brunMoyen, minWidth: 40 }}>{v.heure}</span>
              <Badge bg={v.origine === "Comptoir" ? C.or : C.brunMoyen}>{v.origine}</Badge>
              <span style={{ flex: 1, fontSize: 11.5, color: C.brunMoyen, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                {v.items.map(i => `${i.qte}× ${FICHES.find(f => f.id === i.ficheId)?.nom || "?"}`).join(", ")}
              </span>
              <span style={{ fontSize: 11, color: C.brunMoyen }}>{v.mode}</span>
              {v.refOperateur && <span style={{ fontSize: 9.5, color: C.vert, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700 }}>✓ {v.refOperateur}</span>}
              <b style={{ minWidth: 60, textAlign: "right" }}>{v.total.toFixed(2)} €</b>
            </div>
          ))}
        </Carte>
      )}
    </div>
  );
}

// ============ EXTENSION : Z & ANALYSE (clôture et data de fin de service) ============
const Z_HISTORIQUE_INIT = [
  { id: 1, date: "01/05", caTTC: 1452.29, couverts: 62, modes: { "CB": 1064.53, "Espèces": 313.46, "Ticket resto": 74.3, "Chèque": 0 } },
  { id: 2, date: "02/05", caTTC: 2072.29, couverts: 77, modes: { "CB": 1472.22, "Espèces": 432.01, "Ticket resto": 126.61, "Chèque": 41.45 } },
  { id: 3, date: "03/05", caTTC: 1588.05, couverts: 63, modes: { "CB": 1116.69, "Espèces": 392.05, "Ticket resto": 79.31, "Chèque": 0 } },
  { id: 5, date: "05/05", caTTC: 986.08, couverts: 41, modes: { "CB": 759.98, "Espèces": 127.03, "Ticket resto": 79.35, "Chèque": 19.72 } },
  { id: 6, date: "06/05", caTTC: 1142.33, couverts: 44, modes: { "CB": 846.27, "Espèces": 241.49, "Ticket resto": 54.57, "Chèque": 0 } },
  { id: 7, date: "07/05", caTTC: 1050.1, couverts: 44, modes: { "CB": 747.26, "Espèces": 216.34, "Ticket resto": 86.5, "Chèque": 0 } },
  { id: 8, date: "08/05", caTTC: 1542.85, couverts: 59, modes: { "CB": 1179.27, "Espèces": 226.8, "Ticket resto": 136.78, "Chèque": 0 } },
  { id: 9, date: "09/05", caTTC: 1977.55, couverts: 75, modes: { "CB": 1531.06, "Espèces": 282.18, "Ticket resto": 164.31, "Chèque": 0 } },
  { id: 10, date: "10/05", caTTC: 1928.2, couverts: 83, modes: { "CB": 1402.47, "Espèces": 382.14, "Ticket resto": 105.03, "Chèque": 38.56 } },
  { id: 12, date: "12/05", caTTC: 867.46, couverts: 37, modes: { "CB": 636.16, "Espèces": 169.03, "Ticket resto": 62.27, "Chèque": 0 } },
  { id: 13, date: "13/05", caTTC: 968.07, couverts: 40, modes: { "CB": 708.66, "Espèces": 175.35, "Ticket resto": 84.06, "Chèque": 0 } },
  { id: 14, date: "14/05", caTTC: 1170.02, couverts: 49, modes: { "CB": 921.39, "Espèces": 192.27, "Ticket resto": 56.36, "Chèque": 0 } },
  { id: 15, date: "15/05", caTTC: 1641.34, couverts: 64, modes: { "CB": 1258.64, "Espèces": 260.86, "Ticket resto": 121.84, "Chèque": 0 } },
  { id: 16, date: "16/05", caTTC: 2147.76, couverts: 89, modes: { "CB": 1511.71, "Espèces": 516.26, "Ticket resto": 119.79, "Chèque": 0 } },
  { id: 17, date: "17/05", caTTC: 1582.69, couverts: 59, modes: { "CB": 1274.33, "Espèces": 220.15, "Ticket resto": 88.21, "Chèque": 0 } },
  { id: 19, date: "19/05", caTTC: 929.34, couverts: 34, modes: { "CB": 701.71, "Espèces": 178.15, "Ticket resto": 49.48, "Chèque": 0 } },
  { id: 20, date: "20/05", caTTC: 1044.55, couverts: 43, modes: { "CB": 804.46, "Espèces": 151.42, "Ticket resto": 88.67, "Chèque": 0 } },
  { id: 21, date: "21/05", caTTC: 998.5, couverts: 36, modes: { "CB": 760.0, "Espèces": 174.05, "Ticket resto": 44.48, "Chèque": 19.97 } },
  { id: 22, date: "22/05", caTTC: 1166.21, couverts: 45, modes: { "CB": 927.19, "Espèces": 144.44, "Ticket resto": 71.26, "Chèque": 23.32 } },
  { id: 23, date: "23/05", caTTC: 1848.03, couverts: 68, modes: { "CB": 1410.96, "Espèces": 273.42, "Ticket resto": 163.65, "Chèque": 0 } },
  { id: 24, date: "24/05", caTTC: 1443.04, couverts: 55, modes: { "CB": 1128.18, "Espèces": 218.39, "Ticket resto": 96.47, "Chèque": 0 } },
  { id: 26, date: "26/05", caTTC: 1022.57, couverts: 43, modes: { "CB": 769.15, "Espèces": 189.32, "Ticket resto": 64.1, "Chèque": 0 } },
  { id: 27, date: "27/05", caTTC: 1170.34, couverts: 48, modes: { "CB": 889.54, "Espèces": 223.53, "Ticket resto": 57.27, "Chèque": 0 } },
  { id: 28, date: "28/05", caTTC: 1285.03, couverts: 53, modes: { "CB": 998.05, "Espèces": 196.45, "Ticket resto": 90.53, "Chèque": 0 } },
  { id: 29, date: "29/05", caTTC: 1518.76, couverts: 60, modes: { "CB": 1205.04, "Espèces": 182.32, "Ticket resto": 101.02, "Chèque": 30.38 } },
  { id: 30, date: "30/05", caTTC: 1804.36, couverts: 78, modes: { "CB": 1464.22, "Espèces": 188.69, "Ticket resto": 151.45, "Chèque": 0 } },
  { id: 31, date: "31/05", caTTC: 1650.26, couverts: 71, modes: { "CB": 1329.06, "Espèces": 144.04, "Ticket resto": 144.15, "Chèque": 33.01 } },
];
// Quantités vendues sur le mois par fiche (données simulées, cohérentes avec les Z de mai)
const STATS_PLATS_INIT = {
  1: 412, 2: 96, 3: 154, 4: 88, 5: 61,            // Entrées : accras stars, soupe en retrait
  6: 318, 7: 142, 8: 97, 9: 73, 10: 226, 11: 105, 12: 64, 13: 88, // Plats : colombo & boucané devant, blaff discret
  14: 187, 15: 264,                                  // Accompagnements
  16: 198, 17: 92, 18: 141, 19: 117, 20: 58,        // Desserts : flan coco leader, sorbet à la traîne
};

// Mois précédent (avril 2026) pour comparaison de la synthèse mensuelle
const MOIS_PRECEDENT = { libelle: "Avril", caTTC: 34578.24, couverts: 1413 };

function ZAnalyse({ rapportsZ, setRapportsZ, ventes }) {
  const [saisie, setSaisie] = useState({ caTTC: "", couverts: "", CB: "", "Espèces": "", "Ticket resto": "", "Chèque": "" });
  const [confirme, setConfirme] = useState(false);
  const [lectureZ, setLectureZ] = useState(false);
  const [erreurLecture, setErreurLecture] = useState(null);
  const fichierRef = useRef(null);

  // Lecture du ticket Z photographié par l'IA (vision)
  const lireTicketZ = async (file) => {
    if (!file) return;
    setLectureZ(true);
    setErreurLecture(null);
    try {
      const base64 = await new Promise((res, rej) => {
        const r = new FileReader();
        r.onload = () => res(r.result.split(",")[1]);
        r.onerror = () => rej(new Error("lecture impossible"));
        r.readAsDataURL(file);
      });
      const response = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          model: "claude-sonnet-4-20250514",
          max_tokens: 600,
          messages: [{
            role: "user",
            content: [
              { type: "image", source: { type: "base64", media_type: file.type || "image/jpeg", data: base64 } },
              { type: "text", text: `Ceci est un ticket Z (rapport de clôture) d'une caisse de restaurant. Extrais les totaux et réponds UNIQUEMENT avec un JSON valide, sans backticks ni texte autour, au format exact :
{"caTTC":<nombre>,"couverts":<nombre ou 0>,"CB":<nombre>,"Especes":<nombre>,"TicketResto":<nombre>,"Cheque":<nombre>}
Règles : caTTC = total TTC du jour. couverts = nombre de couverts ou de tickets/clients si présent, sinon 0. Répartis les règlements par mode (carte bancaire → CB, espèces/cash → Especes, titres/tickets restaurant → TicketResto, chèques → Cheque) ; mets 0 si absent. Nombres décimaux avec un point.` }
            ],
          }],
        }),
      });
      const data = await response.json();
      const txt = (data.content || []).filter(b => b.type === "text").map(b => b.text).join("").replace(/```json|```/g, "").trim();
      const z = JSON.parse(txt);
      setSaisie({
        caTTC: String(z.caTTC ?? ""), couverts: String(z.couverts ?? ""),
        CB: String(z.CB ?? 0), "Espèces": String(z.Especes ?? 0),
        "Ticket resto": String(z.TicketResto ?? 0), "Chèque": String(z.Cheque ?? 0),
      });
    } catch {
      setErreurLecture("Lecture impossible — photo floue ou format inattendu. Réessaie ou saisis manuellement.");
    }
    setLectureZ(false);
  };

  // ---- Données Mizen Pro de la journée (copie de gestion) pour pré-remplissage et rapprochement ----
  const caChefAI = ventes.reduce((s, v) => s + v.total, 0);
  const modesChefAI = {};
  for (const v of ventes) {
    const m = String(v.mode).split(" · ")[0];
    modesChefAI[m] = (modesChefAI[m] || 0) + v.total;
  }

  const preRemplir = () => setSaisie({
    caTTC: caChefAI.toFixed(2), couverts: String(ventes.length),
    CB: (modesChefAI["CB"] || 0).toFixed(2), "Espèces": (modesChefAI["Espèces"] || 0).toFixed(2),
    "Ticket resto": (modesChefAI["Ticket resto"] || 0).toFixed(2), "Chèque": (modesChefAI["Chèque"] || 0).toFixed(2),
  });

  const num = (x) => parseFloat(String(x).replace(",", ".")) || 0;
  const totalModes = MODES_PAIEMENT.reduce((s, m) => s + num(saisie[m]), 0);
  const ecartInterne = num(saisie.caTTC) - totalModes; // cohérence du Z saisi
  const ecartChefAI = num(saisie.caTTC) - caChefAI;    // rapprochement Z officiel vs copie Mizen Pro

  const cloturer = () => {
    if (num(saisie.caTTC) <= 0) return;
    setRapportsZ([...rapportsZ, {
      id: Date.now(),
      date: new Date().toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit" }),
      caTTC: num(saisie.caTTC), couverts: parseInt(saisie.couverts, 10) || 0,
      modes: Object.fromEntries(MODES_PAIEMENT.map(m => [m, num(saisie[m])])),
    }]);
    setSaisie({ caTTC: "", couverts: "", CB: "", "Espèces": "", "Ticket resto": "", "Chèque": "" });
    setConfirme(true);
    setTimeout(() => setConfirme(false), 3000);
  };

  // ---- Analyse de l'historique ----
  const histo = rapportsZ;
  const caMoyen = histo.length ? histo.reduce((s, z) => s + z.caTTC, 0) / histo.length : 0;
  const meilleurJour = histo.length ? histo.reduce((a, b) => a.caTTC > b.caTTC ? a : b) : null;
  const ticketMoyenGlobal = histo.length ? histo.reduce((s, z) => s + z.caTTC, 0) / Math.max(1, histo.reduce((s, z) => s + z.couverts, 0)) : 0;
  const partCB = histo.length ? histo.reduce((s, z) => s + (z.modes["CB"] || 0), 0) / Math.max(1, histo.reduce((s, z) => s + z.caTTC, 0)) * 100 : 0;
  const maxCA = Math.max(...histo.map(z => z.caTTC), 1);
  const tendance = histo.length >= 4
    ? (histo.slice(-2).reduce((s, z) => s + z.caTTC, 0) / 2) - (histo.slice(0, 2).reduce((s, z) => s + z.caTTC, 0) / 2)
    : 0;

  const champStyle = { width: "100%", padding: "8px 10px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, background: C.blanc, color: C.brun, boxSizing: "border-box" };

  return (
    <div>
      {/* ===== SAISIE DU Z DE FIN DE SERVICE ===== */}
      <Carte style={{ marginBottom: 16, borderTop: `4px solid ${C.or}` }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
          <h3 style={{ ...h3Style, margin: 0 }}>Clôture du service — saisie du Z</h3>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            <input ref={fichierRef} type="file" accept="image/*" capture="environment" style={{ display: "none" }}
              onChange={e => { lireTicketZ(e.target.files?.[0]); e.target.value = ""; }} />
            <button onClick={() => fichierRef.current?.click()} disabled={lectureZ} style={{ ...btnStyle(C.or), marginTop: 0 }}>
              {lectureZ ? "👁 L'IA lit le ticket…" : "📷 Photographier le ticket Z"}
            </button>
            <button onClick={preRemplir} disabled={!ventes.length} style={{ ...btnStyle(C.brunMoyen), marginTop: 0, opacity: ventes.length ? 1 : 0.45 }}>
              ⚡ Pré-remplir depuis Mizen Pro
            </button>
          </div>
        </div>
        <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, margin: "6px 0 12px" }}>
          Le Z provient de ta caisse tierce (SumUp, Zettle, L'Addition, caisse traditionnelle…) : photographie le ticket et l'IA le lit, ou saisis les totaux. Mizen Pro rapproche, archive et analyse — il ne remplace pas ta caisse, il l'exploite.
        </div>
        {erreurLecture && (
          <div style={{ padding: 8, marginBottom: 10, background: "#F3DCD4", border: `1.5px solid ${C.rouille}`, borderRadius: 8, fontSize: 12, fontFamily: "Inter, system-ui, sans-serif", color: C.rouille }}>
            ⚠ {erreurLecture}
          </div>
        )}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))", gap: 10 }}>
          <div>
            <label style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontWeight: 700, textTransform: "uppercase" }}>CA TTC du Z</label>
            <input value={saisie.caTTC} onChange={e => setSaisie({ ...saisie, caTTC: e.target.value })} placeholder="0,00 €" style={champStyle} />
          </div>
          <div>
            <label style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontWeight: 700, textTransform: "uppercase" }}>Couverts / tickets</label>
            <input value={saisie.couverts} onChange={e => setSaisie({ ...saisie, couverts: e.target.value })} placeholder="0" style={champStyle} />
          </div>
          {MODES_PAIEMENT.map(m => (
            <div key={m}>
              <label style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontWeight: 700, textTransform: "uppercase" }}>{m}</label>
              <input value={saisie[m]} onChange={e => setSaisie({ ...saisie, [m]: e.target.value })} placeholder="0,00 €" style={champStyle} />
            </div>
          ))}
        </div>
        {/* Rapprochements */}
        {num(saisie.caTTC) > 0 && (
          <div style={{ display: "flex", gap: 10, flexWrap: "wrap", marginTop: 12 }}>
            <div style={{ flex: 1, minWidth: 200, padding: 10, borderRadius: 8, background: Math.abs(ecartInterne) < 0.01 ? "#EDF2E9" : "#F3DCD4", border: `1.5px solid ${Math.abs(ecartInterne) < 0.01 ? C.vert : C.rouille}`, fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, color: C.brun }}>
              <b>Cohérence du Z :</b> {Math.abs(ecartInterne) < 0.01 ? "✓ la somme des modes = CA TTC" : `⚠ écart de ${ecartInterne.toFixed(2)} € entre le CA et la somme des paiements`}
            </div>
            <div style={{ flex: 1, minWidth: 200, padding: 10, borderRadius: 8, background: Math.abs(ecartChefAI) < 0.01 ? "#EDF2E9" : "#F5EAD3", border: `1.5px solid ${Math.abs(ecartChefAI) < 0.01 ? C.vert : C.or}`, fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, color: C.brun }}>
              <b>Rapprochement Mizen Pro :</b> {ventes.length === 0 ? "aucune vente Mizen Pro aujourd'hui" : Math.abs(ecartChefAI) < 0.01 ? "✓ identique à la copie de gestion" : `écart de ${ecartChefAI > 0 ? "+" : ""}${ecartChefAI.toFixed(2)} € vs Mizen Pro (${caChefAI.toFixed(2)} €) — vente hors app ou oubli ?`}
            </div>
          </div>
        )}
        <button onClick={cloturer} disabled={num(saisie.caTTC) <= 0} style={{ ...btnStyle(confirme ? C.vert : C.brun), width: "100%", padding: "12px", fontSize: 13.5, marginTop: 12, opacity: num(saisie.caTTC) > 0 ? 1 : 0.45 }}>
          {confirme ? "✓ Z enregistré — bonne soirée Chef !" : "🌙 Clôturer le service et enregistrer le Z"}
        </button>
      </Carte>

      {/* ===== ANALYSE ===== */}
      {histo.length > 0 && (
        <>
          <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
            <Stat label="CA moyen / service" valeur={`${caMoyen.toFixed(0)} €`} sous={`Sur ${histo.length} services`} />
            <Stat label="Ticket moyen" valeur={`${ticketMoyenGlobal.toFixed(2)} €`} sous="Par couvert" />
            <Stat label="Part CB" valeur={`${partCB.toFixed(0)}%`} sous="Du CA total" accent={C.brunMoyen} />
            <Stat label="Tendance" valeur={tendance >= 0 ? `+${tendance.toFixed(0)} €` : `${tendance.toFixed(0)} €`} sous="Derniers vs premiers services" accent={tendance >= 0 ? C.vert : C.rouille} />
          </div>

          <Carte style={{ marginBottom: 14 }}>
            <h3 style={h3Style}>CA par service</h3>
            <div style={{ display: "flex", alignItems: "flex-end", gap: 8, height: 150, padding: "0 4px" }}>
              {histo.slice(-14).map(z => (
                <div key={z.id} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
                  <span style={{ fontSize: 9.5, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, color: C.brun }}>{z.caTTC.toFixed(0)}</span>
                  <div style={{
                    width: "100%", maxWidth: 44, borderRadius: "6px 6px 0 0",
                    height: `${Math.max(8, z.caTTC / maxCA * 110)}px`,
                    background: meilleurJour && z.id === meilleurJour.id ? C.or : C.brunMoyen,
                  }} />
                  <span style={{ fontSize: 9.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>{z.date}</span>
                </div>
              ))}
            </div>
            {meilleurJour && (
              <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, marginTop: 8 }}>
                🏆 Meilleur service : <b>{meilleurJour.date}</b> avec <b style={{ color: C.or }}>{meilleurJour.caTTC.toFixed(2)} €</b> ({meilleurJour.couverts} couverts)
              </div>
            )}
          </Carte>

          <Carte>
            <h3 style={h3Style}>Historique des Z</h3>
            {[...histo].reverse().map(z => (
              <div key={z.id} style={{ display: "flex", gap: 10, alignItems: "center", padding: "6px 0", borderBottom: `1px dashed ${C.orClair}`, fontSize: 12, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, flexWrap: "wrap" }}>
                <b style={{ minWidth: 44 }}>{z.date}</b>
                <span style={{ minWidth: 80, fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.or }}>{z.caTTC.toFixed(2)} €</span>
                <span style={{ color: C.brunMoyen }}>{z.couverts} couverts · {z.couverts ? (z.caTTC / z.couverts).toFixed(2) : "—"} €/couvert</span>
                <span style={{ flex: 1, textAlign: "right", fontSize: 11, color: C.brunMoyen }}>
                  {MODES_PAIEMENT.filter(m => z.modes[m] > 0).map(m => `${m} ${z.modes[m].toFixed(0)}€`).join(" · ")}
                </span>
              </div>
            ))}
          </Carte>
        </>
      )}
    </div>
  );
}

// ============ EXTENSION : RÉSERVATIONS ============
const RESERVATIONS_INIT = [
  { id: 1, nom: "Mme Lordinot", couverts: 4, heure: "12:30", service: "Midi", tel: "0696 12 34 56", table: null, statut: "a_venir" },
  { id: 2, nom: "M. Ozier-Lafontaine", couverts: 2, heure: "13:00", service: "Midi", tel: "0696 98 76 54", table: 7, statut: "a_venir" },
  { id: 3, nom: "Anniversaire Solène (gâteau prévu)", couverts: 8, heure: "20:00", service: "Soir", tel: "0696 55 44 33", table: 8, statut: "a_venir" },
  { id: 4, nom: "M. Cassius", couverts: 2, heure: "19:30", service: "Soir", tel: "0696 22 11 00", table: null, statut: "a_venir" },
];

function Reservations({ reservations, setReservations, tables, setTables }) {
  const [form, setForm] = useState({ nom: "", couverts: "2", heure: "", service: "Soir", tel: "" });
  const champ = { padding: "8px 10px", border: `1.5px solid ${C.orClair}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, background: C.blanc, color: C.brun, boxSizing: "border-box" };

  const ajouter = () => {
    if (!form.nom.trim() || !form.heure.trim()) return;
    setReservations([...reservations, {
      id: Date.now(), nom: form.nom.trim(), couverts: parseInt(form.couverts, 10) || 2,
      heure: form.heure.trim(), service: form.service, tel: form.tel.trim(), table: null, statut: "a_venir",
    }]);
    setForm({ nom: "", couverts: "2", heure: "", service: form.service, tel: "" });
  };

  const installer = (resa, numTable) => {
    if (!numTable) return;
    setReservations(reservations.map(r => r.id === resa.id ? { ...r, table: Number(numTable), statut: "installee" } : r));
    setTables(tables.map(t => t.num === Number(numTable) ? { ...t, statut: "occupee" } : t));
  };

  const annuler = (id) => setReservations(reservations.filter(r => r.id !== id));
  const assigner = (resa, numTable) => setReservations(reservations.map(r => r.id === resa.id ? { ...r, table: numTable ? Number(numTable) : null } : r));

  const couvertsService = (srv) => reservations.filter(r => r.service === srv && r.statut !== "annulee").reduce((s, r) => s + r.couverts, 0);
  const capacite = tables.reduce((s, t) => s + t.places, 0);
  const tablesLibres = tables.filter(t => t.statut === "libre");

  const ligneResa = (r) => (
    <div key={r.id} style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 0", borderBottom: `1px dashed ${C.orClair}`, flexWrap: "wrap" }}>
      <b style={{ fontFamily: "Fraunces, Georgia, serif", color: C.brun, fontSize: 14, minWidth: 44 }}>{r.heure}</b>
      <div style={{ flex: 1, minWidth: 140 }}>
        <div style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 13, fontWeight: 700, color: C.brun }}>{r.nom}</div>
        <div style={{ fontSize: 11, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>{r.couverts} couverts{r.tel ? ` · ${r.tel}` : ""}</div>
      </div>
      {r.statut === "installee" ? (
        <Badge bg={C.vert}>Installée · T{r.table}</Badge>
      ) : (
        <>
          <select value={r.table || ""} onChange={e => assigner(r, e.target.value)} style={{ ...champ, padding: "6px 8px", fontSize: 12 }}>
            <option value="">Table ?</option>
            {tables.filter(t => t.places >= r.couverts || t.num === r.table).map(t => (
              <option key={t.num} value={t.num}>T{t.num} ({t.places} cv){t.statut === "occupee" ? " · occupée" : ""}</option>
            ))}
          </select>
          <button onClick={() => installer(r, r.table)} disabled={!r.table} style={{ ...btnStyle(C.vert), marginTop: 0, opacity: r.table ? 1 : 0.45 }}>Installer</button>
          <button onClick={() => annuler(r.id)} style={{ ...btnStyle(C.rouille), marginTop: 0, padding: "7px 10px" }}>✕</button>
        </>
      )}
    </div>
  );

  return (
    <div>
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
        <Stat label="Couverts midi" valeur={couvertsService("Midi")} sous={`Capacité salle : ${capacite}`} accent={couvertsService("Midi") > capacite ? C.rouille : C.or} />
        <Stat label="Couverts soir" valeur={couvertsService("Soir")} sous={`Capacité salle : ${capacite}`} accent={couvertsService("Soir") > capacite ? C.rouille : C.or} />
        <Stat label="Tables libres" valeur={tablesLibres.length} sous={`Sur ${tables.length} tables`} accent={C.vert} />
      </div>

      {/* Nouvelle réservation */}
      <Carte style={{ marginBottom: 14 }}>
        <h3 style={h3Style}>Nouvelle réservation</h3>
        <div style={{ display: "grid", gridTemplateColumns: "2fr 70px 80px 100px 1.4fr auto", gap: 8, alignItems: "end" }}>
          <input placeholder="Nom (et note : allergie, occasion…)" value={form.nom} onChange={e => setForm({ ...form, nom: e.target.value })} style={champ} />
          <input placeholder="Cv" value={form.couverts} onChange={e => setForm({ ...form, couverts: e.target.value })} style={champ} />
          <input placeholder="19:30" value={form.heure} onChange={e => setForm({ ...form, heure: e.target.value })} style={champ} />
          <select value={form.service} onChange={e => setForm({ ...form, service: e.target.value })} style={champ}>
            <option>Midi</option><option>Soir</option>
          </select>
          <input placeholder="Téléphone" value={form.tel} onChange={e => setForm({ ...form, tel: e.target.value })} style={champ} />
          <button onClick={ajouter} style={{ ...btnStyle(C.or), marginTop: 0, padding: "9px 16px" }}>＋ Réserver</button>
        </div>
      </Carte>

      {["Midi", "Soir"].map(srv => {
        const liste = reservations.filter(r => r.service === srv).sort((a, b) => a.heure.localeCompare(b.heure));
        return (
          <Carte key={srv} style={{ marginBottom: 12 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <h3 style={{ ...h3Style, margin: 0 }}>{srv === "Midi" ? "☀ Service du midi" : "🌙 Service du soir"}</h3>
              <Badge bg={C.brunMoyen}>{couvertsService(srv)} couverts</Badge>
            </div>
            <div style={{ marginTop: 6 }}>
              {!liste.length && <div style={{ fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, fontStyle: "italic", padding: "8px 0" }}>Aucune réservation.</div>}
              {liste.map(ligneResa)}
            </div>
          </Carte>
        );
      })}
    </div>
  );
}

// ============ MODULE NATIF : CUISINE (mise en place & production) ============
function Cuisine({ mep, setMep, stocks, setStocks, goTo }) {
  const [saisieDispo, setSaisieDispo] = useState({});

  const validerMep = (fiche) => {
    const dispo = parseInt(saisieDispo[fiche.id] ?? mep[fiche.id]?.dispo ?? 0, 10) || 0;
    if (dispo <= 0) return;
    const deductions = calculerDeductions(stocks, fiche, dispo);
    // Déduire les matières premières de l'inventaire
    setStocks(stocks.map(s => {
      const d = deductions.find(x => x.stockId === s.id);
      return d ? { ...s, qte: Math.max(0, +(s.qte - d.qte).toFixed(2)) } : s;
    }));
    setMep({ ...mep, [fiche.id]: { valide: true, dispo, deductions, prodLe: Date.now() } });
  };

  const annulerMep = (fiche) => {
    const m = mep[fiche.id];
    if (!m) return;
    // Restituer les matières déduites
    setStocks(stocks.map(s => {
      const d = (m.deductions || []).find(x => x.stockId === s.id);
      return d ? { ...s, qte: +(s.qte + d.qte).toFixed(2) } : s;
    }));
    const nv = { ...mep };
    delete nv[fiche.id];
    setMep(nv);
  };

  const valides = Object.values(mep).filter(m => m.valide).length;
  const portionsPretes = Object.values(mep).reduce((s, m) => s + (m.dispo || 0), 0);
  const faibles = FICHES.filter(f => mep[f.id]?.valide && mep[f.id].dispo > 0 && mep[f.id].dispo <= 3);
  const epuises = FICHES.filter(f => mep[f.id]?.valide && mep[f.id].dispo === 0);

  return (
    <div>
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 16 }}>
        <Stat label="Mise en place" valeur={`${valides}/${FICHES.length}`} sous="Plats validés" accent={valides === FICHES.length ? C.vert : C.or} />
        <Stat label="Portions prêtes" valeur={portionsPretes} sous="Disponibles à la vente" />
        <Stat label="Bientôt épuisés" valeur={faibles.length} sous={faibles.length ? faibles.map(f => f.nom).slice(0, 2).join(", ") : "Rien à signaler"} accent={faibles.length ? C.or : C.vert} />
        <Stat label="Épuisés (86)" valeur={epuises.length} sous={epuises.length ? "À annoncer en salle" : "Tout est dispo"} accent={epuises.length ? C.rouille : C.vert} />
      </div>

      {/* ===== MISE EN PLACE DU SERVICE ===== */}
      <Carte style={{ marginBottom: 16, borderTop: `4px solid ${C.or}` }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 6 }}>
          <h3 style={{ ...h3Style, margin: 0 }}>Mise en place du service</h3>
          <span style={{ fontSize: 12, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen }}>
            {Object.values(mep).filter(m => m.valide).length}/{FICHES.length} validés · {Object.values(mep).reduce((s, m) => s + (m.dispo || 0), 0)} portions prêtes à la vente
          </span>
        </div>
        <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, margin: "6px 0 10px" }}>
          Saisis le nombre de portions préparées puis valide : les matières premières sont déduites de l'inventaire et les disponibilités apparaissent en salle.
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 8 }}>
          {FICHES.map(f => {
            const m = mep[f.id];
            return (
              <div key={f.id} style={{
                display: "flex", alignItems: "center", gap: 8, padding: "8px 10px",
                background: m?.valide ? "#EDF2E9" : C.creme, borderRadius: 8,
                border: `1.5px solid ${m?.valide ? C.vert : C.orClair}`,
              }}>
                <button onClick={() => m?.valide ? annulerMep(f) : validerMep(f)} style={{
                  width: 24, height: 24, borderRadius: "50%", flexShrink: 0,
                  border: `2px solid ${m?.valide ? C.vert : C.brunMoyen}`,
                  background: m?.valide ? C.vert : C.blanc, color: C.blanc,
                  fontSize: 13, fontWeight: 700, cursor: "pointer", lineHeight: 1,
                }}>{m?.valide ? "✓" : ""}</button>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 12.5, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700, color: C.brun, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{f.nom}</div>
                  <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: m?.valide ? C.vert : C.brunMoyen }}>
                    {m?.valide ? `${m.dispo} dispo à la vente` : "À valider avant service"}
                  </div>
                </div>
                {!m?.valide && (
                  <input value={saisieDispo[f.id] || ""} onChange={e => setSaisieDispo({ ...saisieDispo, [f.id]: e.target.value })}
                    placeholder="Qté" style={{
                      width: 44, padding: "5px 4px", border: `1.5px solid ${C.orClair}`, borderRadius: 6,
                      fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, textAlign: "center", background: C.blanc, color: C.brun,
                    }} />
                )}
                {m?.valide && <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 17, color: m.dispo <= 3 ? C.rouille : C.vert }}>{m.dispo}</span>}
              </div>
            );
          })}
        </div>
      </Carte>

      <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
        <button onClick={() => goTo("fiches")} style={btnStyle(C.brunMoyen)}>✦ Consulter une fiche technique →</button>
        <button onClick={() => goTo("stocks")} style={btnStyle(C.brunMoyen)}>▤ Vérifier l'inventaire →</button>
        <button onClick={() => goTo("haccp")} style={btnStyle(C.brunMoyen)}>❄ Relevés de température →</button>
      </div>
    </div>
  );
}

// ============ MODULE NATIF : FOOD COST (analyse & menu engineering) ============
function FoodCost({ ventes, factures, rapportsZ }) {
  const [simId, setSimId] = useState(null);
  const [simPrix, setSimPrix] = useState("");
  const [cible, setCible] = useState("30");

  // Quantités vendues (mois + jour en direct)
  const compteur = { ...STATS_PLATS_INIT };
  for (const v of ventes) for (const i of v.items) compteur[i.ficheId] = (compteur[i.ficheId] || 0) + i.qte;

  const lignes = FICHES.map(f => {
    const vendus = compteur[f.id] || 0;
    const fc = f.coutPortion / f.prixVente * 100;
    const marge = f.prixVente - f.coutPortion;
    return { ...f, vendus, fc, marge, margeTotale: marge * vendus, caPlat: f.prixVente * vendus, coutTotal: f.coutPortion * vendus };
  });

  // ---- FOOD COST GLOBAL PONDÉRÉ PAR LES VENTES (théorique) ----
  const caTheorique = lignes.reduce((s, l) => s + l.caPlat, 0);
  const coutTheorique = lignes.reduce((s, l) => s + l.coutTotal, 0);
  const fcGlobal = caTheorique ? coutTheorique / caTheorique * 100 : 0;
  const cibleNum = parseFloat(cible.replace(",", ".")) || 30;

  // ---- FOOD COST RÉEL : achats scannés / CA des Z ----
  const achatsReels = factures.reduce((s, x) => s + x.montantTTC, 0);
  const caZ = rapportsZ.reduce((s, z) => s + z.caTTC, 0);
  const fcReel = achatsReels > 0 && caZ > 0 ? achatsReels / caZ * 100 : null;

  // ---- MENU ENGINEERING : médiane popularité × médiane marge ----
  const tries = [...lignes].sort((a, b) => a.vendus - b.vendus);
  const medVendus = tries[Math.floor(tries.length / 2)]?.vendus || 0;
  const triesMarge = [...lignes].sort((a, b) => a.marge - b.marge);
  const medMarge = triesMarge[Math.floor(triesMarge.length / 2)]?.marge || 0;
  const classer = (l) => {
    const pop = l.vendus >= medVendus, rentable = l.marge >= medMarge;
    if (pop && rentable) return "star";
    if (pop && !rentable) return "cheval";
    if (!pop && rentable) return "enigme";
    return "poids";
  };
  const QUADRANTS = [
    { id: "star", titre: "⭐ Stars", soustitre: "Populaires & rentables — mettez-les en avant", couleur: C.vert, fond: "#EDF3E8" },
    { id: "cheval", titre: "🐴 Chevaux de labour", soustitre: "Populaires, marge faible — montez le prix ou baissez le coût", couleur: C.or, fond: "#F8F0DC" },
    { id: "enigme", titre: "❓ Énigmes", soustitre: "Rentables mais boudés — suggestion serveur, photo, position carte", couleur: C.brunMoyen, fond: "#F2EBDD" },
    { id: "poids", titre: "🪨 Poids morts", soustitre: "Ni vendus ni rentables — retravailler ou sortir de carte", couleur: C.rouille, fond: "#F6E4D9" },
  ];

  // ---- SIMULATEUR ----
  const fSim = lignes.find(l => l.id === simId);
  const prixSim = fSim ? (parseFloat(simPrix.replace(",", ".")) || fSim.prixVente) : 0;
  const prixPourCible = (f) => f.coutPortion / (cibleNum / 100);

  return (
    <div>
      {/* ===== INDICATEURS GLOBAUX ===== */}
      <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
        <Stat label="Food cost théorique" valeur={`${fcGlobal.toFixed(1)}%`} sous="Pondéré par les ventes du mois" accent={fcGlobal <= cibleNum ? C.vert : C.rouille} />
        <Stat label="Food cost réel" valeur={fcReel !== null ? `${fcReel.toFixed(1)}%` : "—"} sous={fcReel !== null ? "Factures scannées / CA des Z" : "Scanne tes factures fournisseurs"} accent={fcReel !== null ? (fcReel <= cibleNum + 3 ? C.vert : C.rouille) : C.brunMoyen} />
        <Carte style={{ flex: 1, minWidth: 150, borderTop: `3px solid ${C.or}` }}>
          <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: 1, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>Objectif food cost</div>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 6 }}>
            <input value={cible} onChange={e => setCible(e.target.value)} style={{ width: 56, padding: "7px 8px", border: `1.5px solid ${C.orClair}`, borderRadius: 8, fontFamily: "Fraunces, Georgia, serif", fontSize: 18, fontWeight: 700, background: C.blanc, color: C.brun, textAlign: "center" }} />
            <span style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 18, fontWeight: 700, color: C.brun }}>%</span>
          </div>
        </Carte>
        {fcReel !== null && (
          <Stat label="Écart réel − théorique" valeur={`${(fcReel - fcGlobal) >= 0 ? "+" : ""}${(fcReel - fcGlobal).toFixed(1)} pt`} sous={fcReel - fcGlobal > 4 ? "Pertes, coulure ou vol ?" : "Maîtrisé"} accent={fcReel - fcGlobal > 4 ? C.rouille : C.vert} />
        )}
      </div>

      {/* ===== MENU ENGINEERING ===== */}
      <Carte style={{ marginBottom: 14 }}>
        <h3 style={h3Style}>Menu engineering — la matrice de votre carte</h3>
        <div style={{ fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginBottom: 12 }}>
          Chaque plat est classé selon sa popularité (vendus vs médiane : {medVendus}) et sa marge brute (vs médiane : {medMarge.toFixed(2)} €).
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {QUADRANTS.map(q => {
            const plats = lignes.filter(l => classer(l) === q.id).sort((a, b) => b.margeTotale - a.margeTotale);
            return (
              <div key={q.id} style={{ padding: 12, borderRadius: 12, background: q.fond, border: `1.5px solid ${q.couleur}` }}>
                <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 14, color: q.couleur }}>{q.titre} ({plats.length})</div>
                <div style={{ fontSize: 10, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginBottom: 8 }}>{q.soustitre}</div>
                {plats.slice(0, 5).map(p => (
                  <div key={p.id} style={{ display: "flex", justifyContent: "space-between", fontSize: 11.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brun, padding: "3px 0" }}>
                    <span>{p.nom}</span>
                    <span style={{ color: C.brunMoyen }}>{p.vendus} · {p.marge.toFixed(2)} €</span>
                  </div>
                ))}
                {plats.length > 5 && <div style={{ fontSize: 10, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif" }}>+ {plats.length - 5} autre(s)</div>}
              </div>
            );
          })}
        </div>
      </Carte>

      {/* ===== TABLE DÉTAILLÉE + SIMULATEUR ===== */}
      <Carte>
        <h3 style={h3Style}>Détail par plat — touchez un plat pour simuler un prix</h3>
        <div style={{ overflowX: "auto" }}>
          <table style={{ borderCollapse: "collapse", width: "100%", fontFamily: "Inter, system-ui, sans-serif", fontSize: 12 }}>
            <thead>
              <tr style={{ color: C.brunMoyen, fontSize: 10.5, textTransform: "uppercase" }}>
                {["Plat", "Coût", "PV", "FC %", "Marge", "Vendus", "Marge totale", "PV pour " + cibleNum + "%"].map(h => (
                  <th key={h} style={{ textAlign: h === "Plat" ? "left" : "right", padding: "6px 8px", borderBottom: `2px solid ${C.orClair}` }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {[...lignes].sort((a, b) => b.margeTotale - a.margeTotale).map(l => {
                const estSim = simId === l.id;
                const fcAffiche = estSim ? l.coutPortion / prixSim * 100 : l.fc;
                return (
                  <tr key={l.id} onClick={() => { setSimId(estSim ? null : l.id); setSimPrix(String(l.prixVente)); }}
                    style={{ cursor: "pointer", background: estSim ? "#F8F0DC" : "transparent", borderBottom: `1px dashed ${C.orClair}` }}>
                    <td style={{ padding: "7px 8px", fontWeight: 600, color: C.brun }}>{l.nom}</td>
                    <td style={{ padding: "7px 8px", textAlign: "right", color: C.brunMoyen }}>{l.coutPortion.toFixed(2)} €</td>
                    <td style={{ padding: "7px 8px", textAlign: "right" }}>
                      {estSim ? (
                        <input value={simPrix} onChange={e => setSimPrix(e.target.value)} onClick={e => e.stopPropagation()}
                          style={{ width: 62, padding: "4px 6px", border: `1.5px solid ${C.or}`, borderRadius: 6, fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, textAlign: "right", background: C.blanc, color: C.brun }} />
                      ) : <b>{l.prixVente.toFixed(2)} €</b>}
                    </td>
                    <td style={{ padding: "7px 8px", textAlign: "right", fontWeight: 700, color: fcAffiche <= cibleNum ? C.vert : C.rouille }}>{fcAffiche.toFixed(1)}%</td>
                    <td style={{ padding: "7px 8px", textAlign: "right" }}>{(estSim ? prixSim - l.coutPortion : l.marge).toFixed(2)} €</td>
                    <td style={{ padding: "7px 8px", textAlign: "right", color: C.brunMoyen }}>{l.vendus}</td>
                    <td style={{ padding: "7px 8px", textAlign: "right", fontWeight: 700, color: C.or }}>{(estSim ? (prixSim - l.coutPortion) * l.vendus : l.margeTotale).toFixed(0)} €</td>
                    <td style={{ padding: "7px 8px", textAlign: "right", color: C.brunMoyen, fontStyle: "italic" }}>{prixPourCible(l).toFixed(2)} €</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        {fSim && (
          <div style={{ marginTop: 10, padding: 10, borderRadius: 10, background: "#F8F0DC", border: `1.5px solid ${C.or}`, fontSize: 12, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
            <b>Simulation {fSim.nom}</b> : à {prixSim.toFixed ? prixSim.toFixed(2) : prixSim} €, le food cost passe à <b style={{ color: fSim.coutPortion / prixSim * 100 <= cibleNum ? C.vert : C.rouille }}>{(fSim.coutPortion / prixSim * 100).toFixed(1)}%</b> et la marge mensuelle (à volume constant) à <b style={{ color: C.or }}>{((prixSim - fSim.coutPortion) * fSim.vendus).toFixed(0)} €</b> ({(((prixSim - fSim.coutPortion) * fSim.vendus) - fSim.margeTotale) >= 0 ? "+" : ""}{(((prixSim - fSim.coutPortion) * fSim.vendus) - fSim.margeTotale).toFixed(0)} €). Pour atteindre {cibleNum}% : vendre à {prixPourCible(fSim).toFixed(2)} €.
          </div>
        )}
        <div style={{ fontSize: 10.5, fontFamily: "Inter, system-ui, sans-serif", color: C.brunMoyen, marginTop: 8 }}>
          Prix TTC, coûts matière HT estimés — affinez les coûts dans vos fiches techniques. La colonne « PV pour {cibleNum}% » donne le prix de vente atteignant votre objectif.
        </div>
      </Carte>
    </div>
  );
}

// ============ PAGE D'ACCUEIL (lanceur d'applications) ============
function Accueil({ extensions, releves, commandes, stocks, reservations, mep, aller, lancerOrdre }) {
  const [ordre, setOrdre] = useState("");
  const pastilles = {
    haccp: releves.filter(r => r.statut === "alerte").length + Object.entries(mep).filter(([, m]) => m.valide && m.dispo > 0 && Math.ceil(((m.prodLe || Date.now()) + 3 * 86400000 - Date.now()) / 86400000) <= 1).length,
    service: commandes.filter(c => c.statut === "en_attente" || c.statut === "en_cuisine").length,
    stocks: stocks.filter(s => s.qte <= s.seuil).length,
    fournisseurs: produitsACommander(stocks).length,
    resa: reservations.filter(r => r.statut === "a_venir").length,
    cuisine: Math.max(0, FICHES.length - Object.values(mep).filter(m => m.valide).length) > 0 ? FICHES.length - Object.values(mep).filter(m => m.valide).length : 0,
  };
  const tuiles = [...extensions, { id: "boutique", label: "Extensions", icone: "+", boutique: true }];
  return (
    <div>
      <div style={{ textAlign: "center", margin: "18px 0 20px" }}>
        <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 22, color: C.brun }}>
          Bonjour Chef <span style={{ color: C.or }}>👨‍🍳</span>
        </div>
        <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 13, color: C.brunMoyen, fontStyle: "italic", marginTop: 4 }}>
          {new Date().toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long" })} — La Kaz Créole
        </div>
      </div>
      <div style={{ display: "flex", gap: 8, maxWidth: 540, margin: "0 auto 28px" }}>
        <input value={ordre} onChange={e => setOrdre(e.target.value)}
          onKeyDown={e => { if (e.key === "Enter" && ordre.trim()) { lancerOrdre(ordre.trim()); setOrdre(""); } }}
          placeholder="✺ Demande au pilote : « réserve 4 couverts ce soir 20h »…"
          style={{ flex: 1, padding: "12px 16px", border: "1.5px solid rgba(200,150,42,0.45)", borderRadius: 24, fontFamily: "Inter, system-ui, sans-serif", fontSize: 13.5, background: C.blanc, color: C.brun, boxShadow: "0 2px 10px rgba(53,39,24,0.07)" }} />
        <button onClick={() => { if (ordre.trim()) { lancerOrdre(ordre.trim()); setOrdre(""); } }}
          style={{ ...btnStyle(C.brun), marginTop: 0, borderRadius: 24, padding: "12px 20px" }}>→</button>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(118px, 1fr))", gap: 18, maxWidth: 760, margin: "0 auto" }}>
        {tuiles.map(ext => {
          const n = pastilles[ext.id] || 0;
          return (
            <button key={ext.id} onClick={() => aller(ext.id)} style={{
              background: "transparent", border: "none", cursor: "pointer", padding: 0,
              display: "flex", flexDirection: "column", alignItems: "center", gap: 8,
            }}>
              <div style={{
                position: "relative", width: 90, height: 90, borderRadius: 26,
                background: ext.boutique
                  ? C.blanc
                  : `linear-gradient(145deg, ${C.brun} 0%, #56401F 100%)`,
                border: ext.boutique ? `2px dashed ${C.or}` : "1px solid rgba(200,150,42,0.6)",
                boxShadow: ext.boutique ? "0 2px 8px rgba(53,39,24,0.10)" : "0 8px 20px rgba(53,39,24,0.28), inset 0 1px 0 rgba(230,204,133,0.35)",
                display: "flex", alignItems: "center", justifyContent: "center",
                fontSize: 37, color: ext.boutique ? C.or : C.orClair,
                transition: "transform .12s",
              }}
                onMouseDown={e => e.currentTarget.style.transform = "scale(0.93)"}
                onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
                onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}
              >
                {ext.icone}
                {n > 0 && (
                  <span style={{
                    position: "absolute", top: -7, right: -7, minWidth: 24, height: 24, borderRadius: 12,
                    background: C.rouille, color: C.blanc, fontSize: 12.5, fontWeight: 700, fontFamily: "Inter, system-ui, sans-serif",
                    display: "flex", alignItems: "center", justifyContent: "center", padding: "0 6px",
                    border: `2px solid ${C.creme}`,
                  }}>{n}</span>
                )}
              </div>
              <span style={{ fontFamily: "Inter, system-ui, sans-serif", fontSize: 12, fontWeight: 700, color: C.brun, textAlign: "center", lineHeight: 1.25, maxWidth: 100 }}>
                {ext.label}
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ============ REGISTRE DES EXTENSIONS (architecture type Kodi) ============
// Chaque module est une "extension" : le cœur de l'app ne connaît que ce registre.
// Demain, ajouter un module = ajouter une entrée ici, zéro modification du cœur.
const EXTENSIONS = [
  { id: "dashboard", label: "Tableau de bord", icone: "◉", desc: "KPIs de l'établissement : food cost, alertes HACCP, marges par plat.", prix: "Inclus", coeur: true, composant: Dashboard },
  { id: "fiches", label: "Fiches techniques", icone: "✦", desc: "Recettes pro, food cost, multiplicateur de portions, allergènes.", prix: "Inclus", coeur: true, composant: FichesTechniques },
  { id: "cuisine", label: "Cuisine", icone: "♨", desc: "La mise en place du service : validez vos productions, les matières sont déduites de l'inventaire et les dispos partent en salle.", prix: "Inclus", coeur: true, composant: Cuisine },
  { id: "foodcost", label: "Food cost", icone: "％", desc: "Food cost théorique vs réel, menu engineering (stars, poids morts…) et simulateur de prix par plat.", prix: "Inclus", coeur: true, composant: FoodCost },
  { id: "haccp", label: "HACCP", icone: "❄", desc: "Registre de températures, alertes de conformité, traçabilité PMS.", prix: "19 €/mois", coeur: false, composant: Haccp },
  { id: "fournisseurs", label: "Fournisseurs", icone: "⬡", desc: "Carnet fournisseurs, délais de livraison, historique de commandes.", prix: "9 €/mois", coeur: false, composant: Fournisseurs },
  { id: "ia", label: "Assistant IA", icone: "✺", desc: "L'agent pilote, au texte et à la voix : il lit et agit sur tous vos modules, mains libres pendant le service.", prix: "29 €/mois", coeur: false, composant: AssistantIA },
  { id: "stocks", label: "Inventaire", icone: "▤", desc: "Tous les consommables Cuisine, Salle et Bar : alertes de rupture, valorisation, déduction auto à la mise en place.", prix: "Inclus", coeur: true, composant: Stocks },
  { id: "planning", label: "Planning équipe", icone: "◷", desc: "Planning hebdo interactif, masse salariale et ratio personnel/CA.", prix: "15 €/mois", coeur: false, composant: Planning },
  { id: "carte", label: "Menus & carte du jour", icone: "✎", desc: "Composez un menu depuis vos fiches : carte élégante + food cost auto.", prix: "Inclus", coeur: true, composant: Menus },
  { id: "caisse", label: "Encaissement connecté", icone: "€", desc: "Préparez le ticket dans Mizen Pro, encaissez sur la caisse certifiée de votre opérateur (SumUp, Zettle…). Zéro double saisie, conformité assurée par l'opérateur.", prix: "29 €/mois", coeur: false, composant: Caisse },
  { id: "zanalyse", label: "Z & Analyse", icone: "◫", desc: "Importez le Z de votre caisse (photo lue par l'IA ou saisie), rapprochement auto avec Mizen Pro, historique et tendances. Compatible toute caisse.", prix: "15 €/mois", coeur: false, composant: ZAnalyse },
  { id: "service", label: "Service en salle", icone: "⬚", desc: "Plan de salle interactif, prise de commande par table, écran cuisine (KDS).", prix: "25 €/mois", coeur: false, composant: Service },
  { id: "resa", label: "Réservations", icone: "✆", desc: "Carnet de réservations midi/soir, jauge de couverts vs capacité, installation directe sur le plan de salle.", prix: "12 €/mois", coeur: false, composant: Reservations },
];

function Boutique({ actives, toggle }) {
  const totalMensuel = EXTENSIONS.filter(e => actives.includes(e.id) && !e.coeur && !e.aVenir)
    .reduce((s, e) => s + (parseFloat(String(e.prix).replace(",", ".")) || 0), 0);
  return (
    <div>
      <Carte style={{ marginBottom: 14, background: C.cremeFonce }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
          <div style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, color: C.brun, fontSize: 16 }}>Boutique d'extensions</div>
          <span style={{ fontFamily: "Fraunces, Georgia, serif", fontWeight: 700, fontSize: 15, color: C.brun, background: C.blanc, border: `1.5px solid ${C.or}`, borderRadius: 12, padding: "5px 14px" }}>
            Votre configuration : <span style={{ color: C.or }}>{totalMensuel.toFixed(0)} €/mois</span>
          </span>
        </div>
        <div style={{ fontSize: 13, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", marginTop: 4, lineHeight: 1.5 }}>
          Composez votre Mizen Pro : les modules cœur sont inclus, activez uniquement les extensions dont votre établissement a besoin. Le total s'ajuste en direct.
        </div>
      </Carte>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", gap: 12 }}>
        {EXTENSIONS.map(ext => {
          const installee = actives.includes(ext.id);
          return (
            <Carte key={ext.id} style={{ borderTop: `3px solid ${ext.aVenir ? C.brunMoyen : installee ? C.vert : C.or}`, opacity: ext.aVenir ? 0.75 : 1, display: "flex", flexDirection: "column" }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                <span style={{ fontSize: 22, color: C.or }}>{ext.icone}</span>
                <Badge bg={ext.aVenir ? C.brunMoyen : installee ? C.vert : C.or}>
                  {ext.aVenir ? "À venir" : installee ? "Installée" : ext.prix}
                </Badge>
              </div>
              <h3 style={{ fontFamily: "Fraunces, Georgia, serif", color: C.brun, margin: "10px 0 6px", fontSize: 16 }}>{ext.label}</h3>
              <div style={{ fontSize: 12.5, color: C.brunMoyen, fontFamily: "Inter, system-ui, sans-serif", lineHeight: 1.5, flex: 1 }}>{ext.desc}</div>
              {ext.coeur ? (
                <div style={{ marginTop: 10, fontSize: 11, color: C.vert, fontFamily: "Inter, system-ui, sans-serif", fontWeight: 700 }}>✓ Module cœur — toujours actif</div>
              ) : ext.aVenir ? (
                <button disabled style={{ ...btnStyle(C.brunMoyen), width: "100%", marginTop: 10, opacity: 0.5, cursor: "default" }}>Disponible prochainement</button>
              ) : (
                <button onClick={() => toggle(ext.id)} style={{ ...btnStyle(installee ? C.rouille : C.vert), width: "100%", marginTop: 10 }}>
                  {installee ? "Désinstaller" : "Installer"}
                </button>
              )}
            </Carte>
          );
        })}
      </div>
    </div>
  );
}

// ============ APP (cœur léger : nav dynamique pilotée par le registre) ============
function ChefAI() {
  const [onglet, setOnglet] = useState("accueil");
  const [actives, setActives] = useState(["dashboard", "fiches", "cuisine", "foodcost", "stocks", "carte", "caisse", "zanalyse", "haccp", "fournisseurs", "resa", "ia"]);
  // ---- ÉTAT CENTRAL DE L'ÉTABLISSEMENT (partagé entre toutes les extensions) ----
  const [stocks, setStocks] = useState(STOCKS_INIT);
  const [releves, setReleves] = useState(RELEVES_INIT);
  const [planning, setPlanning] = useState(planningInitial);
  const [menu, setMenu] = useState({ selection: [6, 1, 16], titre: "Menu du jour", prix: "24" });
  const [carteExclus, setCarteExclus] = useState([]);
  const [ventes, setVentes] = useState([]);
  const [operateur, setOperateur] = useState(null); // opérateur de paiement connecté (id)
  const [rapportsZ, setRapportsZ] = useState(Z_HISTORIQUE_INIT);
  const [factures, setFactures] = useState([]);
  const [reservations, setReservations] = useState(RESERVATIONS_INIT);
  const [ordreInitial, setOrdreInitial] = useState(null);
  const [persistance, setPersistance] = useState("chargement"); // "chargement" | "ok" | "indispo"
  const sauvegardeRef = useRef(null);

  // ---- CHARGEMENT au démarrage ----
  useEffect(() => {
    (async () => {
      if (!window.storage) { setPersistance("indispo"); return; }
      try {
        const res = await window.storage.get("chefai-etat");
        if (res?.value) {
          const e = JSON.parse(res.value);
          if (Array.isArray(e.fiches) && e.fiches.length) FICHES = e.fiches;
          if (e.stocks) setStocks(e.stocks);
          if (e.releves) setReleves(e.releves);
          if (e.planning) setPlanning(e.planning);
          if (e.menu) setMenu(e.menu);
          if (e.tables) setTables(e.tables);
          if (e.commandes) setCommandes(e.commandes);
          if (e.mep) setMep(e.mep);
          if (e.precommandes) setPrecommandes(e.precommandes);
          if (e.carteExclus) setCarteExclus(e.carteExclus);
          if (e.ventes) setVentes(e.ventes);
          if (e.operateur !== undefined) setOperateur(e.operateur);
          if (e.rapportsZ) setRapportsZ(e.rapportsZ);
          if (e.factures) setFactures(e.factures);
          if (e.reservations) setReservations(e.reservations);
          if (e.actives) setActives(e.actives);
        }
        setPersistance("ok");
      } catch { setPersistance("ok"); /* première utilisation : la clé n'existe pas encore */ }
    })();
  }, []);

  // ---- SAUVEGARDE automatique (debounce 1,2s) ----
  useEffect(() => {
    if (persistance !== "ok" || !window.storage) return;
    clearTimeout(sauvegardeRef.current);
    sauvegardeRef.current = setTimeout(async () => {
      try {
        await window.storage.set("chefai-etat", JSON.stringify({
          fiches: FICHES, stocks, releves, planning, menu, tables, commandes, mep,
          precommandes, carteExclus, ventes, operateur, rapportsZ, factures, reservations, actives,
        }));
      } catch { /* sauvegarde silencieusement différée */ }
    }, 1200);
    return () => clearTimeout(sauvegardeRef.current);
  });
  const enregistrerVente = (v) => setVentes(prev => [...prev, {
    ...v, id: Date.now() + Math.random(),
    heure: new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }),
    // Référence de l'enregistrement fiscal chez l'opérateur (renvoyée par son API)
    refOperateur: operateur ? `${operateur.toUpperCase()}-${String(Math.floor(100000 + Math.random() * 900000))}` : null,
  }]);
  const [tables, setTables] = useState(TABLES_INIT);
  const [commandes, setCommandes] = useState([]);
  // Mise en place : { ficheId: { valide, dispo, deductions, prodLe } }
  const [mep, setMep] = useState({
    // Productions de démo encore au frigo (DLC à surveiller)
    5: { valide: true, dispo: 4, deductions: [], prodLe: Date.now() - 2 * 86400000 },
    16: { valide: true, dispo: 6, deductions: [], prodLe: Date.now() - 3 * 86400000 },
  });
  const [precommandes, setPrecommandes] = useState([]);
  const [, setVersionFiches] = useState(0);
  const ajouterFiche = (fiche) => { FICHES = [...FICHES, fiche]; setVersionFiches(v => v + 1); };
  const supprimerFiche = (id) => {
    FICHES = FICHES.filter(f => f.id !== id);
    setMep(prev => { const nv = { ...prev }; delete nv[id]; return nv; });
    setMenu(prev => ({ ...prev, selection: prev.selection.filter(s => s !== id) }));
    setVersionFiches(v => v + 1);
  };

  const toggle = (id) => {
    if (actives.includes(id)) {
      setActives(actives.filter(a => a !== id));
      if (onglet === id) setOnglet("boutique");
    } else {
      setActives([...actives, id]);
    }
  };

  const extensionsActives = EXTENSIONS.filter(e => actives.includes(e.id) && !e.aVenir);
  const onglets = [
    { id: "accueil", label: "Accueil", icone: "⌂" },
    ...extensionsActives.map(e => ({ id: e.id, label: e.label, icone: e.icone })),
    { id: "boutique", label: "Extensions", icone: "+" },
  ];
  const naviguerVers = (id) => {
    if (id === "accueil" || id === "boutique") { setOnglet(id); return; }
    if (!actives.includes(id) && EXTENSIONS.some(e => e.id === id)) setActives(a => [...a, id]);
    setOnglet(id);
  };
  const propsParModule = {
    dashboard: { goTo: naviguerVers, releves, mep, stocks, rapportsZ, reservations, ventes },
    cuisine: { mep, setMep, stocks, setStocks, goTo: naviguerVers },
    foodcost: { ventes, factures, rapportsZ },
    fiches: { supprimerFiche, mep, setMep, stocks, setStocks, goTo: naviguerVers, majFiches: () => setVersionFiches(v => v + 1) },
    haccp: { releves, setReleves, mep, setMep },
    fournisseurs: { stocks, setStocks, precommandes, setPrecommandes, factures, setFactures },
    ia: { stocks, setStocks, releves, setReleves, planning, setPlanning, menu, setMenu, tables, setTables, commandes, setCommandes, mep, setMep, precommandes, setPrecommandes, ventes, rapportsZ, factures, reservations, setReservations, naviguer: naviguerVers, ajouterFiche, ordreInitial, consommerOrdre: () => setOrdreInitial(null) },
    stocks: { stocks, setStocks },
    planning: { planning, setPlanning },
    service: { tables, setTables, commandes, setCommandes, mep, setMep, enregistrerVente, reservations },
    caisse: { ventes, enregistrerVente, mep, setMep, operateur, setOperateur },
    zanalyse: { rapportsZ, setRapportsZ, ventes },
    resa: { reservations, setReservations, tables, setTables },
    carte: {
      selection: menu.selection, setSelection: (s) => setMenu({ ...menu, selection: s }),
      titreMenu: menu.titre, setTitreMenu: (t) => setMenu({ ...menu, titre: t }),
      prixMenu: menu.prix, setPrixMenu: (p) => setMenu({ ...menu, prix: p }),
      carteExclus, setCarteExclus, majFiches: () => setVersionFiches(v => v + 1),
    },
  };
  const extCourante = extensionsActives.find(e => e.id === onglet);
  const ExtCourante = extCourante?.composant;

  return (
    <div style={{ minHeight: "100vh", background: `linear-gradient(180deg, ${C.creme} 0%, #F3ECDB 100%)`, fontFamily: "Inter, system-ui, sans-serif", color: C.brun }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap');
        * { -webkit-font-smoothing: antialiased; box-sizing: border-box; }
        button { transition: transform .12s ease, filter .12s ease, box-shadow .12s ease; }
        button:hover:not(:disabled) { filter: brightness(1.07); }
        button:active:not(:disabled) { transform: scale(0.97); }
        input, select { transition: border-color .15s ease, box-shadow .15s ease; }
        input:focus, select:focus { outline: none; border-color: ${"#C8962A"} !important; box-shadow: 0 0 0 3px rgba(200,150,42,0.18); }
        ::-webkit-scrollbar { width: 8px; height: 8px; }
        ::-webkit-scrollbar-track { background: transparent; }
        ::-webkit-scrollbar-thumb { background: rgba(200,150,42,0.4); border-radius: 4px; }
        ::-webkit-scrollbar-thumb:hover { background: rgba(200,150,42,0.6); }
      `}</style>
      {/* Header */}
      <div style={{ background: `linear-gradient(120deg, ${C.brun} 0%, #4F3A22 70%, #5A431F 100%)`, padding: "15px 22px", display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: `3px solid ${C.or}`, boxShadow: "0 2px 14px rgba(53,39,24,0.25)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <img src="/logo-header.png" alt="Mizen" onError={e => { e.currentTarget.outerHTML = '<div style="width:40px;height:40px;border-radius:12px;border:2px solid #C8962A;display:flex;align-items:center;justify-content:center;font-family:Fraunces,Georgia,serif;font-size:20px;color:#C8962A">⌘</div>'; }} style={{ width: 40, height: 40, borderRadius: 12, boxShadow: "0 2px 8px rgba(0,0,0,0.3)", display: "block" }} />
          <div>
            <div style={{ fontFamily: "Fraunces, Georgia, serif", fontSize: 20, fontWeight: 700, color: C.creme, letterSpacing: 1 }}>Mizen <span style={{ color: C.or }}>Pro</span></div>
            <div style={{ fontSize: 10, color: C.orClair, textTransform: "uppercase", letterSpacing: 2 }}>Le système d'exploitation de votre restaurant</div>
          </div>
        </div>
        <div style={{ fontSize: 12, color: C.orClair, fontFamily: "Fraunces, Georgia, serif", fontStyle: "italic" }}>Restaurant La Kaz Créole · Le François</div>
      </div>
      {/* Nav dynamique */}
      <div style={{ display: "flex", gap: 5, padding: "10px 16px", background: "rgba(255,254,250,0.95)", backdropFilter: "blur(8px)", borderBottom: "1px solid rgba(200,150,42,0.25)", overflowX: "auto", position: "sticky", top: 0, zIndex: 50 }}>
        {onglets.map(o => (
          <button key={o.id} onClick={() => setOnglet(o.id)} style={{
            background: onglet === o.id ? C.or : "transparent",
            color: onglet === o.id ? C.blanc : C.brun,
            border: o.id === "boutique" ? `1.5px dashed ${C.or}` : "none",
            borderRadius: 20, padding: "8px 16px",
            fontSize: 13, fontWeight: 700, cursor: "pointer", whiteSpace: "nowrap",
            fontFamily: "Inter, system-ui, sans-serif",
          }}>{o.icone} {o.label}</button>
        ))}
      </div>
      {/* Contenu */}
      <div style={{ padding: 18, maxWidth: 1100, margin: "0 auto" }}>
        {onglet === "accueil" && <Accueil extensions={extensionsActives} releves={releves} commandes={commandes} stocks={stocks} reservations={reservations} mep={mep} aller={naviguerVers} lancerOrdre={(o) => { setOrdreInitial(o); naviguerVers("ia"); }} />}
        {onglet === "boutique" && <Boutique actives={actives} toggle={toggle} />}
        {ExtCourante && <ExtCourante {...propsParModule[extCourante.id]} />}
      </div>
      <div style={{ textAlign: "center", padding: 14, fontSize: 11, color: C.brunMoyen, fontFamily: "Fraunces, Georgia, serif", fontStyle: "italic" }}>
        Mizen Pro · Prototype MVP · Architecture modulaire · Martinique 🌴
        {persistance === "ok" && <span style={{ marginLeft: 8, color: C.vert }}>● données sauvegardées</span>}
        {persistance === "indispo" && <span style={{ marginLeft: 8 }}>○ session sans sauvegarde</span>}
      </div>
    </div>
  );
}


ReactDOM.createRoot(document.getElementById("root")).render(<ChefAI />);
