<template>
  <div v-uppercase-scope class="layout-px-spacing">
    <teleport to="#breadcrumb">
      <ul class="navbar-nav flex-row">
        <li>
          <div class="page-header">
            <nav class="breadcrumb-one" aria-label="breadcrumb">
              <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="javascript:;">{{ $t("ventas.pos.breadcrumb_parent") }}</a></li>
                <li class="breadcrumb-item active" aria-current="page">
                  <span>{{ $t("ventas.pos.breadcrumb_current") }}</span>
                </li>
              </ol>
            </nav>
          </div>
        </li>
      </ul>
    </teleport>

    <div v-if="posInicializando" class="alert alert-info py-2 small mb-3" role="status">
      {{ $t("ventas.pos.init_loading") }}
    </div>

    <div v-if="cajaCargada && !cajaAbierta" class="alert alert-danger d-flex align-items-center mb-4" role="alert">
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="me-3 flex-shrink-0">
        <circle cx="12" cy="12" r="10"></circle>
        <line x1="12" y1="8" x2="12" y2="12"></line>
        <line x1="12" y1="16" x2="12.01" y2="16"></line>
      </svg>
      <div class="flex-grow-1">
        <strong>{{ $t("ventas.pos.caja_closed_title") }}</strong>
        {{ $t("ventas.pos.caja_closed_body") }}
        <router-link to="/ventas/caja" class="alert-link ms-1">{{ $t("ventas.pos.caja_open_link") }}</router-link>
      </div>
    </div>

    <div class="row layout-top-spacing">
      <div class="col-xl-5 col-lg-5 col-sm-12 layout-spacing">
        <div class="panel lavanderia-panel p-4 pos-catalog-panel">
          <div class="pos-flow-badge mb-2">1 · {{ $t("ventas.pos.flow_step_catalog") }}</div>
          <h4 class="mb-2">{{ $t("ventas.pos.catalog_title") }}</h4>
          <p class="small text-muted mb-3">{{ $t("ventas.pos.catalog_hint") }}</p>

          <div v-if="cargandoCatalogoPos" class="text-center py-3">
            <div class="spinner-border text-primary" role="status"></div>
            <div class="small text-muted mt-2">{{ $t("ventas.pos.loading_catalog") }}</div>
          </div>
          <template v-else>
            <p v-if="!saleProducts.length" class="text-muted small mb-0">{{ $t("ventas.pos.no_products") }}</p>
            <button
              v-else
              type="button"
              class="btn btn-primary w-100 lavanderia-operator-btn pos-add-service-btn flex-column"
              :disabled="!cajaAbierta"
              @click="openCatalogModal"
            >
              <span class="fw-semibold">{{ $t("ventas.pos.open_catalog_modal_btn") }}</span>
              <span class="small opacity-90">{{ $t("ventas.pos.open_catalog_modal_sub") }}</span>
            </button>
          </template>

          <div class="pos-cart-panel mt-3 pt-3 border-top">
            <div class="d-flex justify-content-between align-items-center mb-2">
              <h6 class="mb-0">{{ $t("ventas.pos.cart_lines_title") }}</h6>
              <strong v-if="carritoVisible.length" class="text-primary">${{ totalCarrito.toFixed(2) }}</strong>
            </div>
            <div v-if="!carritoVisible.length" class="pos-cart-empty text-muted small py-3 text-center">
              <p class="mb-1">{{ $t("ventas.pos.empty_cart") }}</p>
              <p class="mb-0 opacity-75">{{ $t("ventas.pos.empty_cart_hint") }}</p>
            </div>
            <ul v-else class="list-unstyled pos-cart-list mb-0">
              <li v-for="item in carritoVisible" :key="item.uid" class="pos-cart-item">
                <div class="pos-cart-item-head">
                  <div class="flex-grow-1 min-w-0">
                    <div class="fw-semibold text-truncate">{{ posLineLabel(item) }}</div>
                    <div class="small text-muted d-flex flex-wrap gap-2 align-items-center mt-1">
                      <span v-if="item.laundry_sale_product_id" class="badge bg-success">{{ $t("ventas.pos.badge_catalog") }}</span>
                      <span v-else class="badge bg-primary">{{ $t("ventas.pos.badge_tibu") }}</span>
                      <span>
                        <template v-if="item.billing_mode === 'by_weight'">
                          {{ formatRealWeight(item) }} kg
                          <span v-if="Math.abs(Number(item.weight_kg || 0) - billingWeightForLine(item)) > 0.001" class="text-muted">
                            · {{ $t("ventas.pos.bill_qty_label", { n: formatBillingWeight(item) }) }}
                          </span>
                        </template>
                        <template v-else>
                          {{ $t("ventas.pos.real_qty_label", { n: item.cantidad }) }}
                          <span v-if="billingQtyForLine(item) !== Math.max(1, Math.round(Number(item.cantidad) || 0))" class="text-muted">
                            · {{ $t("ventas.pos.bill_qty_label", { n: billingQtyForLine(item) }) }}
                          </span>
                        </template>
                      </span>
                      <span>${{ lineSubtotal(item).toFixed(2) }}</span>
                      <button
                        v-if="item.laundry_sale_product_id"
                        type="button"
                        class="btn btn-link btn-sm p-0"
                        @click="openContenidoModalCart(item)"
                      >
                        <template v-if="countContenidoForParent(item.uid) > 0">
                          {{ $t("ventas.pos.view_content_btn", { n: countContenidoForParent(item.uid) }) }}
                        </template>
                        <template v-else>{{ $t("ventas.pos.content_modal_btn") }}</template>
                      </button>
                    </div>
                  </div>
                  <div class="d-flex flex-column gap-1 ms-2">
                    <button
                      type="button"
                      class="btn btn-sm btn-outline-secondary"
                      :title="$t('ventas.pos.edit_line')"
                      @click="toggleCartItemExpand(item.uid)"
                    >
                      {{ expandedCartUid === item.uid ? "−" : "+" }}
                    </button>
                    <button type="button" class="btn btn-sm btn-outline-danger" @click="quitarDelCarrito(item)">
                      ×
                    </button>
                  </div>
                </div>
                <div v-if="expandedCartUid === item.uid" class="pos-cart-item-body small">
                  <div v-if="item.laundry_sale_product_id" class="row g-2">
                    <div class="col-6">
                      <label class="form-label mb-0">{{ $t("ventas.pos.real_qty") }}</label>
                      <input
                        v-if="item.billing_mode !== 'by_weight'"
                        type="number"
                        min="1"
                        step="1"
                        class="form-control form-control-sm"
                        v-model.number="item.cantidad"
                        @change="onTibuCantidadChange(item)"
                      />
                      <div v-else class="form-control form-control-sm bg-light">1</div>
                      <div v-if="item.billing_mode !== 'by_weight'" class="form-text mb-0">
                        {{ $t("ventas.pos.bill_qty_label", { n: billingQtyForLine(item) }) }}
                      </div>
                    </div>
                    <div v-if="item.billing_mode === 'by_weight'" class="col-6">
                      <label class="form-label mb-0">{{ $t("ventas.pos.real_weight_kg") }}</label>
                      <input
                        type="number"
                        step="0.1"
                        :min="LAUNDRY_WEIGHT_MIN_KG"
                        class="form-control form-control-sm"
                        v-model.number="item.weight_kg"
                        @change="onWeightKgChange(item)"
                      />
                      <div class="form-text mb-0">
                        {{ $t("ventas.pos.bill_weight_label", { n: formatBillingWeight(item) }) }}
                      </div>
                    </div>
                    <div class="col-12">
                      <div class="form-check">
                        <input
                          :id="`posCustomPrice-${item.uid}`"
                          v-model="item.use_custom_price"
                          class="form-check-input"
                          type="checkbox"
                          @change="onCustomPriceToggle(item)"
                        />
                        <label class="form-check-label" :for="`posCustomPrice-${item.uid}`">
                          {{ $t("ventas.pos.custom_price_enable") }}
                        </label>
                      </div>
                    </div>
                    <div v-if="item.use_custom_price" class="col-6">
                      <label class="form-label mb-0">{{ $t("ventas.pos.custom_price") }}</label>
                      <input
                        type="number"
                        min="0"
                        step="0.01"
                        class="form-control form-control-sm"
                        v-model.number="item.custom_unit_price"
                        @change="syncCartPreviewTotals"
                      />
                    </div>
                    <div v-else class="col-6">
                      <label class="form-label mb-0">{{ $t("ventas.pos.th_unit") }}</label>
                      <div class="form-control form-control-sm bg-light">${{ lineUnitDisplay(item).toFixed(2) }}</div>
                    </div>
                    <div class="col-12">
                      <label class="form-label mb-0">{{ $t("ventas.pos.line_obs") }}</label>
                      <textarea class="form-control form-control-sm" rows="2" v-model="item.observations" maxlength="2000" />
                    </div>
                  </div>
                  <div v-else class="row g-2">
                    <div class="col-12">
                      <label class="form-label mb-0">{{ $t("ventas.pos.garment_type") }}</label>
                      <select class="form-select form-select-sm" v-model.number="item.garment_type_id" @change="onCartGarmentChange(item)">
                        <option :value="null">{{ $t("ventas.pos.select") }}</option>
                        <option v-for="g in garmentTypes" :key="g.id" :value="g.id">{{ g.name }}</option>
                      </select>
                    </div>
                    <div class="col-6">
                      <label class="form-label mb-0">{{ $t("ventas.pos.th_billing_mode") }}</label>
                      <select class="form-select form-select-sm" v-model="item.billing_mode" @change="onLineBillingChange(item)">
                        <option value="by_garment_type">{{ $t("ventas.pos.billing_garment") }}</option>
                        <option value="by_weight">{{ $t("ventas.pos.billing_weight") }}</option>
                      </select>
                    </div>
                    <div class="col-6">
                      <label class="form-label mb-0">{{ $t("ventas.pos.th_qty") }}</label>
                      <input
                        v-if="item.billing_mode !== 'by_weight'"
                        type="number"
                        min="1"
                        class="form-control form-control-sm"
                        v-model.number="item.cantidad"
                        @change="onTibuCantidadChange(item)"
                      />
                      <input
                        v-else
                        type="number"
                        step="0.1"
                        :min="LAUNDRY_WEIGHT_MIN_KG"
                        class="form-control form-control-sm"
                        v-model.number="item.weight_kg"
                        @change="onWeightKgChange(item)"
                      />
                    </div>
                    <div class="col-12">
                      <label class="form-label mb-0">{{ $t("ventas.pos.line_obs") }}</label>
                      <textarea class="form-control form-control-sm" rows="2" v-model="item.observations" maxlength="2000" />
                    </div>
                    <div v-if="tibuLinePriceWarning(item)" class="col-12">
                      <span class="text-warning">{{ tibuLinePriceWarning(item) }}</span>
                    </div>
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>

      <div class="col-xl-7 col-lg-7 col-sm-12 layout-spacing">
        <div class="panel lavanderia-panel p-4">
          <div class="pos-flow-badge mb-2">2 · {{ $t("ventas.pos.flow_step_client") }}</div>
          <h4 class="mb-3">{{ $t("ventas.pos.cart_title") }}</h4>
          <div class="pos-open-orders-panel mb-3">
            <div class="d-flex flex-wrap align-items-center justify-content-end gap-2 mb-2">
              <button type="button" class="btn btn-sm btn-outline-primary" :disabled="creando || !cajaAbierta" @click="nuevaOrdenVacia">
                + {{ $t("ventas.pos.new_order") }}
              </button>
            </div>
            <div v-if="ordenAbierta" class="alert alert-info py-2 mb-2 small d-flex flex-wrap align-items-center justify-content-between gap-2">
              <div>
                <strong>{{ $t("ventas.pos.open_order_badge") }}</strong> {{ ordenAbierta.folio_unico }}
                <span class="text-muted ms-1">— {{ ordenAbierta.cliente_nombre }}</span>
              </div>
              <div class="d-flex flex-wrap gap-2">
                <button
                  v-if="ordenAbierta?.id"
                  type="button"
                  class="btn btn-sm btn-outline-primary text-nowrap"
                  :disabled="imprimiendoTicketTermico !== null || creando"
                  @click="imprimirTicketTermicoOrdenAbierta"
                >
                  {{
                    imprimiendoTicketTermico === ordenAbierta.id
                      ? $t("ventas.pos.print_thermal_printing")
                      : $t("ventas.pos.print_thermal_ticket")
                  }}
                </button>
                <button
                  v-if="ordenAbierta?.id"
                  type="button"
                  class="btn btn-sm btn-dark text-nowrap"
                  :disabled="imprimiendoBolsasPdf !== null || aplicandoBolsas || creando || !clienteSeleccionado?.id"
                  @click="imprimirEtiquetaBolsaRapida"
                >
                  {{
                    imprimiendoBolsasPdf !== null || aplicandoBolsas
                      ? $t("ventas.pos.bags_printing")
                      : $t("ventas.pos.bags_quick_print_btn")
                  }}
                </button>
              </div>
            </div>
            <div class="border rounded p-2 bg-light">
            <label class="form-label mb-1">{{ $t("ventas.pos.reopen_order_label") }}</label>
            <div class="input-group input-group-sm">
              <input
                type="text"
                class="form-control"
                v-model.trim="folioReapertura"
                list="recent-open-order-folios"
                :placeholder="$t('ventas.pos.reopen_order_placeholder')"
                @keyup.enter="reabrirOrdenPorFolio"
              />
              <button type="button" class="btn btn-outline-secondary" :disabled="buscandoOrdenByFolio" @click="reabrirOrdenPorFolio">
                {{ buscandoOrdenByFolio ? $t("ventas.pos.loading_catalog") : $t("ventas.pos.reopen_order_btn") }}
              </button>
            </div>
            <datalist id="recent-open-order-folios">
              <option v-for="f in foliosRecientes" :key="f" :value="f"></option>
            </datalist>
            <div v-if="foliosRecientes.length" class="form-text mb-0">{{ $t("ventas.pos.reopen_order_recent_hint") }}</div>
            </div>
          </div>

          <div class="mb-3 position-relative">
            <label class="form-label">{{ $t("ventas.pos.search_client") }}</label>
            <div class="input-group">
              <input
                type="text"
                class="form-control"
                v-model="busquedaCliente"
                @input="buscarClientes"
                @focus="mostrarSugerencias = true"
                :placeholder="$t('ventas.pos.search_placeholder')"
              />
              <button type="button" class="btn btn-outline-secondary" @click="abrirModalNuevoCliente" :title="$t('ventas.pos.quick_client')">
                + {{ $t("ventas.pos.client") }}
              </button>
            </div>
            <ul
              v-if="mostrarSugerencias && sugerenciasClientes.length > 0"
              class="list-group position-absolute w-100 shadow-sm"
              style="z-index: 10; max-height: 200px; overflow-y: auto"
            >
              <li
                v-for="c in sugerenciasClientes"
                :key="c.id"
                class="list-group-item list-group-item-action"
                @click="seleccionarCliente(c)"
              >
                <strong>{{ c.nombre }}</strong>
                <span
                  v-if="c.cliente_categoria"
                  class="badge ms-2"
                  :style="clienteCategoriaBadgeStyle(c.cliente_categoria)"
                >
                  {{ c.cliente_categoria.name }}
                </span>
                <span v-if="c.email" class="text-muted small ms-2">{{ c.email }}</span>
              </li>
            </ul>
            <div v-if="clienteSeleccionado" class="mt-2 d-flex align-items-center gap-2 flex-wrap">
              <span class="badge bg-primary fs-6">{{ clienteSeleccionado.nombre }}</span>
              <span
                v-if="clienteSeleccionado.cliente_categoria"
                class="badge"
                :style="clienteCategoriaBadgeStyle(clienteSeleccionado.cliente_categoria)"
              >
                {{ clienteSeleccionado.cliente_categoria.name }}
              </span>
              <span v-if="clienteSeleccionado.email" class="text-muted small">{{ clienteSeleccionado.email }}</span>
              <button type="button" class="btn btn-sm btn-outline-secondary" @click="quitarCliente">
                {{ $t("ventas.pos.remove") }}
              </button>
            </div>
            <div v-else class="alert alert-warning py-2 small mb-0 mt-2">
              {{ $t("ventas.pos.client_pick_required_hint") }}
            </div>
            <div
              v-if="v$.clienteSeleccionadoId.$error || serverErrors.cliente_id"
              class="invalid-feedback d-block mt-1"
            >
              <span v-if="v$.clienteSeleccionadoId.$error">{{ v$.clienteSeleccionadoId.$errors[0]?.$message }}</span>
              <span v-for="(msg, i) in serverErrors.cliente_id || []" v-else :key="i">{{ msg }}</span>
            </div>
          </div>

          <div v-if="ordenAbierta?.id" class="mb-3 border rounded p-3 bg-light">
            <div class="d-flex flex-wrap align-items-start justify-content-between gap-2 mb-2">
              <div>
                <h6 class="mb-1">{{ $t("ventas.pos.bags_section_title") }}</h6>
                <p class="small text-muted mb-0">{{ $t("ventas.pos.bags_section_hint") }}</p>
              </div>
              <button
                type="button"
                class="btn btn-outline-dark btn-sm text-nowrap"
                :disabled="imprimiendoBolsasPdf !== null || aplicandoBolsas || creando || !clienteSeleccionado?.id"
                @click="imprimirEtiquetaBolsaRapida"
              >
                {{
                  imprimiendoBolsasPdf !== null || aplicandoBolsas
                    ? $t("ventas.pos.bags_printing")
                    : $t("ventas.pos.bags_quick_print_btn")
                }}
              </button>
            </div>
            <div class="row g-2 align-items-end">
              <div class="col-auto">
                <label class="form-label mb-0">{{ $t("ventas.pos.bags_qty_label") }}</label>
                <input
                  v-model.number="bolsasCantidadDraft"
                  type="number"
                  min="0"
                  max="500"
                  class="form-control"
                  style="width: 7rem"
                  :disabled="aplicandoBolsas || creando"
                />
              </div>
              <div class="col-auto">
                <button
                  type="button"
                  class="btn btn-outline-primary btn-sm"
                  :disabled="aplicandoBolsas || creando"
                  @click="aplicarCantidadBolsasPos"
                >
                  {{ aplicandoBolsas ? $t("ventas.pos.bags_applying") : $t("ventas.pos.bags_apply") }}
                </button>
              </div>
              <div class="col-auto">
                <button
                  type="button"
                  class="btn btn-outline-secondary btn-sm"
                  :disabled="aplicandoBolsas || imprimiendoBolsasPdf !== null || !bolsasListPos.length"
                  @click="imprimirPdfBolsasPos(null)"
                >
                  {{
                    imprimiendoBolsasPdf === "all"
                      ? $t("ventas.pos.bags_printing")
                      : $t("ventas.pos.bags_print_all")
                  }}
                </button>
              </div>
            </div>
            <div v-if="!bolsasListPos.length" class="text-muted small mt-2">{{ $t("ventas.pos.bags_empty") }}</div>
            <ul v-else class="list-unstyled small mt-2 mb-0">
              <li v-for="b in bolsasListPos" :key="b.id" class="d-flex justify-content-between align-items-center py-1 border-bottom">
                <span>
                  <strong>{{ $t("ventas.pos.bags_bag_n", { n: b.indice }) }}</strong>
                  <code class="ms-1">{{ b.codigo }}</code>
                </span>
                <button
                  type="button"
                  class="btn btn-sm btn-link"
                  :disabled="imprimiendoBolsasPdf !== null"
                  @click="imprimirPdfBolsasPos(b.indice)"
                >
                  {{
                    imprimiendoBolsasPdf === b.indice
                      ? $t("ventas.pos.bags_printing")
                      : $t("ventas.pos.bags_print_one")
                  }}
                </button>
              </li>
            </ul>
          </div>

          <div class="mb-2">
            <label class="form-label">{{ $t("ventas.pos.order_tipo_servicio") }} *</label>
            <select
              v-model.number="form.tipo_servicio_id"
              class="form-select"
              :class="{ 'is-invalid': serverErrors.tipo_servicio_id }"
            >
              <option :value="null" disabled>{{ $t("ventas.pos.order_tipo_servicio_placeholder") }}</option>
              <option v-for="ts in tiposServicio" :key="ts.id" :value="ts.id">{{ ts.name }}</option>
            </select>
            <div class="form-text">{{ $t("ventas.pos.order_tipo_servicio_hint") }}</div>
            <div v-if="serverErrors.tipo_servicio_id" class="invalid-feedback d-block">
              {{ Array.isArray(serverErrors.tipo_servicio_id) ? serverErrors.tipo_servicio_id[0] : serverErrors.tipo_servicio_id }}
            </div>
          </div>

          <div class="mb-2">
            <label class="form-label">{{ $t("ventas.pos.semaphore_section") }}</label>
            <div class="row g-2">
              <div class="col-6">
                <label class="form-label small mb-0">{{ $t("ventas.pos.semaphore_yellow") }}</label>
                <input
                  v-model.number="form.semaphore_hours_yellow"
                  type="number"
                  min="0"
                  max="168"
                  step="1"
                  class="form-control"
                  :class="{ 'is-invalid': serverErrors.semaphore_hours_yellow }"
                />
              </div>
              <div class="col-6">
                <label class="form-label small mb-0">{{ $t("ventas.pos.semaphore_red") }}</label>
                <input
                  v-model.number="form.semaphore_hours_red"
                  type="number"
                  min="0"
                  max="168"
                  step="1"
                  class="form-control"
                  :class="{ 'is-invalid': serverErrors.semaphore_hours_red }"
                />
              </div>
            </div>
            <div class="form-text">{{ $t("ventas.pos.semaphore_hint") }}</div>
          </div>

          <div class="mb-2">
            <label class="form-label">{{ $t("ventas.pos.delivery_date") }}</label>
            <input
              v-model="form.fecha_entrega"
              type="datetime-local"
              class="form-control"
              :class="{ 'is-invalid': serverErrors.fecha_entrega }"
            />
            <div class="form-text">{{ $t("ventas.pos.delivery_date_hint") }}</div>
            <div v-if="serverErrors.fecha_entrega" class="invalid-feedback d-block">
              {{ Array.isArray(serverErrors.fecha_entrega) ? serverErrors.fecha_entrega[0] : serverErrors.fecha_entrega }}
            </div>
          </div>

          <div class="mb-2">
            <div class="form-check form-switch">
              <input class="form-check-input" type="checkbox" v-model="form.is_delivery" id="isDelivery" />
              <label class="form-check-label" for="isDelivery">{{ $t("ventas.pos.delivery") }}</label>
            </div>
          </div>

          <div v-if="form.is_delivery" class="mb-2">
            <label class="form-label">{{ $t("ventas.pos.delivery_address") }} *</label>
            <textarea
              class="form-control"
              rows="2"
              v-model="form.delivery_address"
              :class="{ 'is-invalid': v$.delivery_address.$error || serverErrors.delivery_address }"
            ></textarea>
            <div class="form-text">{{ $t("ventas.pos.delivery_address_hint") }}</div>
            <div class="invalid-feedback" v-if="v$.delivery_address.$error">
              {{ v$.delivery_address.$errors[0]?.$message }}
            </div>
            <div
              v-if="clienteSeleccionado?.id && !clienteSeleccionado?.es_mostrador"
              class="form-check mt-2"
            >
              <input
                id="posSaveClientDireccion"
                v-model="form.save_client_direccion"
                class="form-check-input"
                type="checkbox"
              />
              <label class="form-check-label" for="posSaveClientDireccion">
                {{ $t("ventas.pos.save_client_direccion") }}
              </label>
            </div>
          </div>

          <!-- Campo detalles_ropa oculto en POS; el detalle va por línea y observaciones por producto. -->
          <div class="mb-3">
            <label class="form-label">{{ $t("ventas.pos.instructions") }}</label>
            <textarea class="form-control" rows="2" v-model="form.instrucciones" maxlength="2000" />
            <div class="form-check mt-2">
              <input
                id="posMostrarObsTicket"
                v-model="form.mostrar_observaciones_en_ticket"
                class="form-check-input"
                type="checkbox"
              />
              <label class="form-check-label small" for="posMostrarObsTicket">{{ $t("ventas.pos.show_obs_on_ticket") }}</label>
            </div>
          </div>

          <div v-if="carritoVisible.length" class="alert alert-light border py-2 mb-3 small d-flex justify-content-between align-items-center">
            <span>{{ $t("ventas.pos.cart_summary_right", { n: carritoVisible.length }) }}</span>
            <strong>${{ totalCarrito.toFixed(2) }}</strong>
          </div>

          <div v-if="ordenAbierta" class="mb-3 border rounded p-3 bg-light">
            <label class="form-label mb-0">{{ $t("ventas.pos.payments_title") }}</label>
            <p class="small text-muted mb-2">{{ $t("ventas.pos.payments_hint") }}</p>
            <div class="row g-2 mb-1 small text-muted d-none d-md-flex">
              <div class="col-md-5">{{ $t("ventas.pos.payment") }}</div>
              <div class="col-md-4">{{ $t("ventas.pos.payment_amount") }}</div>
              <div class="col-md-3"></div>
            </div>
            <div v-for="(pl, idx) in paymentLines" :key="idx" class="row g-2 align-items-end mb-2">
              <div class="col-md-5">
                <select class="form-select" v-model="pl.metodo_pago">
                  <option value="efectivo">{{ $t("ventas.pos.pay_cash") }}</option>
                  <option value="tarjeta">{{ $t("ventas.pos.pay_card") }}</option>
                  <option value="transferencia">{{ $t("ventas.pos.pay_transfer") }}</option>
                </select>
              </div>
              <div class="col-md-4">
                <input
                  type="number"
                  class="form-control"
                  min="0"
                  step="0.01"
                  v-model.number="pl.monto"
                  :disabled="paymentLines.length === 1"
                />
              </div>
              <div class="col-md-3">
                <button
                  type="button"
                  class="btn btn-outline-danger btn-sm"
                  :disabled="paymentLines.length <= 1"
                  @click="removePaymentLine(idx)"
                >
                  {{ $t("ventas.pos.remove") }}
                </button>
              </div>
            </div>
            <button type="button" class="btn btn-outline-secondary btn-sm" @click="addPaymentLine">
              {{ $t("ventas.pos.add_payment_line") }}
            </button>
            <div v-if="totalCarrito > 0 && paymentLines.length > 1" class="small mt-2" :class="pagosSumanTotal ? 'text-muted' : 'text-warning'">
              {{ $t("ventas.pos.payments_sum_label") }}: ${{ sumaPagosIngresados.toFixed(2) }}
              · {{ $t("ventas.pos.total") }}: ${{ totalCarrito.toFixed(2) }}
            </div>
            <div class="text-end mt-2">
              <strong class="fs-5">{{ $t("ventas.pos.total") }}: ${{ totalCarrito.toFixed(2) }}</strong>
            </div>
          </div>
          <p v-else-if="carritoVisible.length" class="small text-muted mb-3">
            {{ $t("ventas.pos.payments_after_open_hint") }}
          </p>
          <p v-if="carrito.length > 0 && previewTotal != null" class="small text-muted mb-3">
            {{ $t("ventas.pos.preview_sync_notice") }}
          </p>

          <button
            type="button"
            class="btn btn-success btn-lg w-100"
            :disabled="posInicializando || creando || !cajaAbierta || !clienteSeleccionado || hasCartPriceBlock"
            @click="crearOrden"
          >
            {{ creando ? $t("ventas.pos.saving") : (ordenAbierta ? $t("ventas.pos.save_changes") : $t("ventas.pos.open_order")) }}
          </button>
          <button
            v-if="ordenAbierta"
            type="button"
            class="btn btn-outline-primary btn-lg w-100 mt-2"
            :disabled="posInicializando || creando || !cajaAbierta || !clienteSeleccionado || hasCartPriceBlock || blockPaymentsForSubmit"
            @click="finalizarOrdenAbierta"
          >
            {{ $t("ventas.pos.finalize_order") }}
          </button>
        </div>
      </div>
    </div>

    <div v-if="mostrarModalOrden && ordenGenerada" class="modal fade show pos-modal-overlay" tabindex="-1">
      <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">{{ $t("ventas.pos.modal_title", { folio: ordenGenerada.folio_unico }) }}</h5>
            <button type="button" class="btn-close" @click="cerrarModalOrden"></button>
          </div>
          <div class="modal-body">
            <p class="mb-1"><strong>{{ $t("ventas.pos.client_name") }}:</strong> {{ ordenGenerada.cliente_nombre }}</p>
            <p class="mb-1"><strong>{{ $t("ventas.pos.total") }}:</strong> ${{ Number(ordenGenerada.total).toFixed(2) }}</p>
            <div v-if="posQrPreview" class="text-center mb-3 border rounded bg-light py-3 px-2">
              <p class="small text-muted mb-2">{{ $t("ventas.pos.qr_preview_label") }}</p>
              <img
                :src="posQrPreview"
                alt=""
                class="img-fluid rounded border bg-white p-2"
                style="max-width: 200px"
                role="presentation"
              />
              <p class="small text-muted mt-2 mb-0">{{ $t("ventas.pos.qr_preview_hint") }}</p>
            </div>
            <div class="form-check mb-2 text-start">
              <input
                id="posAutoTicket"
                v-model="autoImprimirTicket"
                class="form-check-input"
                type="checkbox"
              />
              <label class="form-check-label small" for="posAutoTicket">{{ $t("ventas.pos.auto_ticket_next") }}</label>
            </div>
            <p class="small text-muted mb-3 text-start">{{ $t("ventas.pos.auto_ticket_hint") }}</p>
            <button type="button" class="btn btn-outline-dark w-100" :disabled="imprimiendoBolsasPdf !== null || aplicandoBolsas" @click="imprimirEtiquetaBolsaOrdenGenerada">
              {{
                imprimiendoBolsasPdf !== null || aplicandoBolsas
                  ? $t("ventas.pos.bags_printing")
                  : $t("ventas.pos.bags_quick_print_btn")
              }}
            </button>
            <button type="button" class="btn btn-outline-primary w-100 mt-2" :disabled="imprimiendoTicketTermico !== null" @click="imprimirTicketTermicoOrdenGenerada">
              {{
                imprimiendoTicketTermico === ordenGenerada?.id
                  ? $t("ventas.pos.print_thermal_printing")
                  : $t("ventas.pos.print_thermal_ticket")
              }}
            </button>
            <button type="button" class="btn btn-primary w-100 mt-2" :disabled="descargandoPdf" @click="imprimirTicketJsPdf">
              {{ descargandoPdf ? $t("ventas.pos.pdf_loading") : $t("ventas.pos.print_two") }}
            </button>
            <button type="button" class="btn btn-outline-secondary w-100 mt-2" :disabled="descargandoPdfServer" @click="imprimirTicketServidor">
              {{ descargandoPdfServer ? $t("ventas.pos.pdf_loading") : $t("ventas.pos.print_server") }}
            </button>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" @click="cerrarModalOrden">{{ $t("ventas.pos.close") }}</button>
          </div>
        </div>
      </div>
    </div>

    <div v-if="modalCatalogOpen" class="modal fade show pos-modal-overlay" tabindex="-1">
      <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-xl pos-catalog-modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <div class="w-100">
              <h5 class="modal-title mb-2">{{ catalogModalTitle }}</h5>
              <div class="pos-catalog-steps d-flex flex-wrap gap-2">
                <span class="pos-catalog-step-pill" :class="{ active: catalogModalStep === 'categories', done: catalogModalStep !== 'categories' }">
                  {{ $t("ventas.pos.catalog_step_categories") }}
                </span>
                <span class="pos-catalog-step-pill" :class="{ active: catalogModalStep === 'services', done: catalogModalStep === 'configure' }">
                  {{ $t("ventas.pos.catalog_step_services") }}
                </span>
                <span class="pos-catalog-step-pill" :class="{ active: catalogModalStep === 'configure' }">
                  {{ $t("ventas.pos.catalog_step_configure") }}
                </span>
              </div>
            </div>
            <button type="button" class="btn-close ms-2" @click="closeCatalogModal"></button>
          </div>
          <div class="modal-body">
            <div v-if="catalogModalStep === 'categories'" class="pos-catalog-step">
              <p class="small text-muted mb-3">{{ $t("ventas.pos.catalog_modal_pick_category") }}</p>
              <div v-if="!posCategoriesWithServices.length" class="text-muted small">{{ $t("ventas.pos.no_products") }}</div>
              <div v-else class="pos-catalog-grid">
                <button
                  v-for="cat in posCategoriesWithServices"
                  :key="cat.id"
                  type="button"
                  class="btn btn-outline-primary pos-catalog-tile"
                  @click="pickCatalogCategory(cat)"
                >
                  <span class="pos-catalog-tile-name">{{ cat.name }}</span>
                </button>
              </div>
            </div>

            <div v-else-if="catalogModalStep === 'services'" class="pos-catalog-step">
              <p class="small text-muted mb-3">
                {{ $t("ventas.pos.catalog_modal_pick_service", { category: catalogModalCategoryName }) }}
              </p>
              <div v-if="!catalogModalServices.length" class="text-muted small">{{ $t("ventas.pos.no_products") }}</div>
              <div v-else class="pos-catalog-grid pos-catalog-grid--services">
                <button
                  v-for="p in catalogModalServices"
                  :key="p.id"
                  type="button"
                  class="btn btn-outline-secondary pos-catalog-tile pos-catalog-tile--service text-start"
                  @click="pickCatalogService(p)"
                >
                  <span class="pos-catalog-tile-name">{{ p.name }}</span>
                  <span class="pos-catalog-tile-meta">
                    <span class="pos-catalog-tile-price">${{ Number(p.unit_price || 0).toFixed(2) }}</span>
                    <span v-if="p.billing_unit === 'kg'" class="badge bg-info">{{ $t("ventas.pos.per_kg") }}</span>
                    <span v-else class="badge bg-secondary">{{ $t("ventas.pos.per_piece") }}</span>
                  </span>
                </button>
              </div>
            </div>

            <div v-else class="pos-catalog-step">
              <p class="small text-muted mb-2">{{ selectedCatalogProduct?.name }}</p>
              <p class="small text-muted mb-3">{{ catalogProductConfigHint }}</p>
              <div class="row g-2">
                <div v-if="selectedCatalogProduct && !catalogBillingIsWeight" class="col-12">
                  <label class="form-label mb-0">{{ $t("ventas.pos.real_qty") }}</label>
                  <input type="number" min="1" step="1" class="form-control form-control-sm" v-model.number="nuevaLineaCatalog.cantidad" />
                  <div class="form-text">
                    {{ $t("ventas.pos.bill_qty_label", { n: billingQtyForCatalogDraft() }) }}
                  </div>
                </div>
                <div v-if="selectedCatalogProduct && catalogBillingIsWeight" class="col-12">
                  <label class="form-label mb-0">{{ $t("ventas.pos.real_weight_kg") }}</label>
                  <input
                    type="number"
                    :min="LAUNDRY_WEIGHT_MIN_KG"
                    step="0.1"
                    class="form-control form-control-sm"
                    v-model.number="nuevaLineaCatalog.weight_kg"
                  />
                  <div class="form-text">
                    {{ $t("ventas.pos.bill_weight_label", { n: formatBillingWeightDraft() }) }}
                  </div>
                </div>
                <div v-if="selectedCatalogProduct" class="col-12">
                  <div class="form-check">
                    <input
                      id="posCatalogCustomPrice"
                      v-model="nuevaLineaCatalog.use_custom_price"
                      class="form-check-input"
                      type="checkbox"
                    />
                    <label class="form-check-label" for="posCatalogCustomPrice">{{ $t("ventas.pos.custom_price_enable") }}</label>
                  </div>
                </div>
                <div v-if="selectedCatalogProduct && nuevaLineaCatalog.use_custom_price" class="col-12">
                  <label class="form-label mb-0">{{ $t("ventas.pos.custom_price") }}</label>
                  <input
                    type="number"
                    min="0"
                    step="0.01"
                    class="form-control form-control-sm"
                    v-model.number="nuevaLineaCatalog.custom_unit_price"
                  />
                </div>
                <div v-else-if="selectedCatalogProduct" class="col-12">
                  <div class="small text-muted">
                    {{ $t("ventas.pos.th_unit") }}: ${{ (previewNuevaLineaCatalogUnit ?? 0).toFixed(2) }}
                  </div>
                </div>
                <div class="col-12">
                  <label class="form-label mb-0">{{ $t("ventas.pos.line_obs") }}</label>
                  <textarea class="form-control form-control-sm" rows="2" v-model="nuevaLineaCatalog.observations" maxlength="2000" />
                </div>
                <div class="col-12">
                  <div v-if="previewNuevaLineaCatalogError" class="alert alert-warning py-2 small mb-0">{{ previewNuevaLineaCatalogError }}</div>
                  <div v-else-if="previewNuevaLineaCatalogUnit != null" class="small text-muted">
                    {{ $t("ventas.pos.th_unit") }}: ${{ previewNuevaLineaCatalogUnit.toFixed(2) }}
                    · {{ $t("ventas.pos.th_sub") }}: ${{ previewNuevaLineaCatalogSub.toFixed(2) }}
                  </div>
                </div>
                <div class="col-12">
                  <button
                    type="button"
                    class="btn btn-outline-secondary w-100 mb-2"
                    :disabled="!cajaAbierta || !selectedCatalogProduct"
                    @click="openContenidoModalDraft"
                  >
                    {{ $t("ventas.pos.content_modal_btn") }}
                    <span v-if="contenidoPendiente.length" class="badge bg-primary ms-1">{{ contenidoPendiente.length }}</span>
                  </button>
                  <p v-if="contenidoPendiente.length && selectedCatalogProduct" class="small text-muted mb-2">
                    {{ $t("ventas.pos.content_pending_hint", { n: contenidoPendiente.length }) }}
                  </p>
                </div>
              </div>
            </div>
          </div>
          <div class="modal-footer">
            <button v-if="catalogModalStep !== 'categories'" type="button" class="btn btn-light" @click="catalogModalBack">
              {{ $t("ventas.pos.catalog_modal_back") }}
            </button>
            <button type="button" class="btn btn-secondary" @click="closeCatalogModal">{{ $t("ventas.pos.cancel") }}</button>
            <button
              v-if="catalogModalStep === 'configure'"
              type="button"
              class="btn btn-success"
              :disabled="!cajaAbierta || addCatalogDisabled"
              @click="confirmarCatalogoModal"
            >
              {{ $t("ventas.pos.add_catalog_btn") }}
            </button>
          </div>
        </div>
      </div>
    </div>

    <div v-if="modalNuevoCliente" class="modal fade show pos-modal-overlay pos-modal-overlay-top" tabindex="-1">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">{{ $t("ventas.pos.quick_client_title") }}</h5>
            <button type="button" class="btn-close" @click="modalNuevoCliente = false"></button>
          </div>
          <div class="modal-body">
            <div class="mb-2">
              <label class="form-label">{{ $t("ventas.pos.client_name") }} *</label>
              <input v-model="nuevoCliente.nombre" class="form-control" />
            </div>
            <div class="mb-2">
              <label class="form-label">{{ $t("ventas.pos.client_email") }}</label>
              <input v-model="nuevoCliente.email" type="email" class="form-control" />
            </div>
            <div class="mb-2">
              <label class="form-label">{{ $t("ventas.pos.phone") }}</label>
              <input v-model="nuevoCliente.telefono" class="form-control" />
            </div>
            <div class="mb-2">
              <label class="form-label">{{ $t("ventas.pos.client_category") }}</label>
              <select v-model="nuevoCliente.cliente_categoria_id" class="form-select">
                <option :value="null">{{ $t("ventas.pos.client_category_none") }}</option>
                <option v-for="cat in categoriasClientes" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
              </select>
            </div>
            <div class="mb-2">
              <label class="form-label">{{ $t("ventas.pos.address") }}</label>
              <input v-model="nuevoCliente.direccion" class="form-control" />
            </div>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" @click="modalNuevoCliente = false">{{ $t("ventas.pos.cancel") }}</button>
            <button type="button" class="btn btn-primary" :disabled="guardandoCliente || !nuevoCliente.nombre" @click="guardarNuevoCliente">
              {{ guardandoCliente ? $t("ventas.pos.saving") : $t("ventas.pos.save_use") }}
            </button>
          </div>
        </div>
      </div>
    </div>

    <div v-if="modalContenidoOpen" class="modal fade show pos-modal-overlay pos-modal-overlay-top" tabindex="-1">
      <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">{{ modalContenidoTitle }}</h5>
            <button type="button" class="btn-close" @click="closeContenidoModal"></button>
          </div>
          <div class="modal-body">
            <p class="small text-muted">{{ modalContenidoSubtitle }}</p>
            <div class="row g-2 border-bottom pb-3 mb-3">
              <div class="col-md-6">
                <label class="form-label mb-0">{{ $t("ventas.pos.garment_type") }}</label>
                <select class="form-select form-select-sm" v-model.number="nuevaLineaContenido.garment_type_id">
                  <option :value="null">{{ $t("ventas.pos.select") }}</option>
                  <option v-for="g in garmentTypes" :key="g.id" :value="g.id">{{ g.name }}</option>
                </select>
              </div>
              <div class="col-md-3">
                <label class="form-label mb-0">{{ $t("ventas.pos.tibu_qty_pieces") }}</label>
                <input type="number" min="1" step="1" class="form-control form-control-sm" v-model.number="nuevaLineaContenido.cantidad" />
              </div>
              <div class="col-md-3 d-flex align-items-end">
                <button type="button" class="btn btn-primary btn-sm w-100" :disabled="addContenidoDisabled" @click="agregarContenidoEnModal">
                  {{ $t("ventas.pos.add_content_btn") }}
                </button>
              </div>
              <div class="col-12">
                <label class="form-label mb-0">{{ $t("ventas.pos.line_obs") }}</label>
                <textarea class="form-control form-control-sm" rows="2" v-model="nuevaLineaContenido.observations" maxlength="2000" />
              </div>
            </div>
            <div v-if="!modalContenidoList.length" class="text-muted small py-2">{{ $t("ventas.pos.content_modal_empty") }}</div>
            <ul v-else class="list-group list-group-flush">
              <li v-for="row in modalContenidoList" :key="row.uid" class="list-group-item px-0 d-flex justify-content-between align-items-start gap-2">
                <div>
                  <strong>{{ garmentName(row.garment_type_id) }}</strong>
                  <span class="text-muted"> × {{ row.cantidad }}</span>
                  <div v-if="row.observations" class="small text-muted">{{ row.observations }}</div>
                </div>
                <button type="button" class="btn btn-sm btn-outline-danger flex-shrink-0" @click="quitarContenidoEnModal(row)">
                  {{ $t("ventas.pos.remove") }}
                </button>
              </li>
            </ul>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" @click="closeContenidoModal">{{ $t("ventas.pos.close") }}</button>
          </div>
        </div>
      </div>
    </div>

    <loading v-model:active="isLoading" :can-cancel="false" :is-full-page="true" />
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, watch, watchEffect } from "vue";
import axios from "axios";
import { useI18n } from "vue-i18n";
import { useVuelidate } from "@vuelidate/core";
import { requiredIf, helpers } from "@vuelidate/validators";
import Loading from "vue-loading-overlay";
import "vue-loading-overlay/dist/css/index.css";
import { useRoute } from "vue-router";
import { puedeEditarOrdenEnPos } from "@/utils/ordenPosEdit";
import { usePageTitle } from "@/composables/use-meta";
import * as OrdenLavanderiaRepository from "@/repositories/OrdenLavanderiaRepository";
import * as CajaRepository from "@/repositories/CajaRepository";
import * as ClienteRepository from "@/repositories/ClienteRepository";
import * as VentasRepository from "@/repositories/VentasRepository";
import * as TicketRepository from "@/repositories/TicketRepository";
import ApiService from "@/services/ApiService";
import { buildSeguimientoQrDataUrl, openTicketPdfTwoQrCopies } from "@/utils/ticketPosPdf";
import { roundLaundryWeightKg, LAUNDRY_WEIGHT_MIN_KG } from "@/utils/laundryWeightKg";
import { billingPieceQuantity, LAUNDRY_PIECE_MIN_BILL_QTY } from "@/utils/laundryPieceQty";
import {
  defaultFechaEntregaLocal,
  datetimeLocalToServerPayload,
  parseIsoToDatetimeLocal,
} from "@/utils/fechaEntrega";

const { t } = useI18n();
const route = useRoute();

usePageTitle(computed(() => t("ventas.pos.page_title")));

const cajaAbierta = ref(false);
const cajaCargada = ref(false);
const posInicializando = ref(true);
const cargandoCatalogoPos = ref(true);
const carrito = ref([]);
const contenidoPendiente = ref([]);
const modalContenidoOpen = ref(false);
const modalContenidoMode = ref("draft");
const modalContenidoParentUid = ref(null);
const etapas = ref([]);
const defaultEtapaId = ref(null);
const laundryPiecePrices = ref([]);
const pricePerKg = ref(null);
const defaultDeliveryDays = ref(3);
const defaultDeliveryHour = ref(20);
const defaultSemaphoreRed = ref(2);
const defaultSemaphoreYellow = ref(3);
const pieceMinBillQty = ref(LAUNDRY_PIECE_MIN_BILL_QTY);
const previewByUid = ref({});
const previewTotal = ref(null);
const busquedaCliente = ref("");
const sugerenciasClientes = ref([]);
const mostrarSugerencias = ref(false);
const clienteSeleccionado = ref(null);
const modalNuevoCliente = ref(false);
const modalCatalogOpen = ref(false);
const catalogModalStep = ref("categories");
const guardandoCliente = ref(false);
const nuevoCliente = ref({ nombre: "", email: "", telefono: "", direccion: "", cliente_categoria_id: null });
const categoriasClientes = ref([]);
const bolsasCantidadDraft = ref(0);
const aplicandoBolsas = ref(false);
const imprimiendoBolsasPdf = ref(null);
const imprimiendoTicketTermico = ref(null);
const receptionPrinterConfigured = ref(false);

const tiposServicio = ref([]);
const garmentTypes = ref([]);
const loadTypes = ref([]);
const saleCategories = ref([]);
const saleProducts = ref([]);

const nuevaLineaCatalog = reactive({
  category_id: null,
  laundry_sale_product_id: null,
  etapa_id: null,
  load_type_id: null,
  billing_mode: "by_garment_type",
  cantidad: 1,
  weight_kg: LAUNDRY_WEIGHT_MIN_KG,
  use_custom_price: false,
  custom_unit_price: null,
  observations: "",
});

const filteredSaleProducts = computed(() => {
  let list = (saleProducts.value || []).filter((p) => p.is_active !== false);
  const cid = nuevaLineaCatalog.category_id;
  if (cid != null && cid !== "") {
    list = list.filter((p) => Number(p.laundry_sale_category_id) === Number(cid));
  }
  return list;
});

const activeSaleProducts = computed(() => (saleProducts.value || []).filter((p) => p.is_active !== false));

const posCategoriesWithServices = computed(() => {
  const catIds = new Set(activeSaleProducts.value.map((p) => Number(p.laundry_sale_category_id)));
  return (saleCategories.value || [])
    .filter((c) => c.is_active !== false && catIds.has(Number(c.id)))
    .sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0) || String(a.name).localeCompare(String(b.name)));
});

const catalogModalServices = computed(() => {
  const cid = nuevaLineaCatalog.category_id;
  if (cid == null || cid === "") return [];
  return activeSaleProducts.value
    .filter((p) => Number(p.laundry_sale_category_id) === Number(cid))
    .sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0) || String(a.name).localeCompare(String(b.name)));
});

const catalogModalCategoryName = computed(() => {
  const c = saleCategories.value.find((x) => Number(x.id) === Number(nuevaLineaCatalog.category_id));
  return c?.name || "";
});

const catalogModalTitle = computed(() => {
  if (catalogModalStep.value === "categories") return t("ventas.pos.catalog_modal_title");
  if (catalogModalStep.value === "services") return t("ventas.pos.catalog_modal_pick_service", { category: catalogModalCategoryName.value });
  return t("ventas.pos.catalog_modal_configure");
});

const selectedCatalogProduct = computed(() => saleProductById(nuevaLineaCatalog.laundry_sale_product_id));

const carritoVisible = computed(() => carrito.value.filter((it) => !it.is_informational));

function defaultTipoServicioId() {
  const normal = tiposServicio.value.find((ts) => ts.code === "normal");
  return normal?.id ?? tiposServicio.value[0]?.id ?? null;
}

function tipoServicioById(id) {
  if (id == null) return null;
  return tiposServicio.value.find((ts) => Number(ts.id) === Number(id)) || null;
}

function isPriorityFromTipoId(tipoId) {
  const ts = tipoServicioById(tipoId);
  return !!(ts && (ts.code === "urgent" || ts.code === "express"));
}

function defaultSemaphoreHours() {
  return {
    red: defaultSemaphoreRed.value,
    yellow: defaultSemaphoreYellow.value,
  };
}

function applyDefaultSemaphoreHours() {
  const d = defaultSemaphoreHours();
  form.semaphore_hours_red = d.red;
  form.semaphore_hours_yellow = d.yellow;
}

function toggleCartItemExpand(uid) {
  expandedCartUid.value = expandedCartUid.value === uid ? null : uid;
}

const modalContenidoList = computed(() => {
  if (modalContenidoMode.value === "draft") return contenidoPendiente.value;
  return carrito.value.filter((it) => it.is_informational && it.parent_uid === modalContenidoParentUid.value);
});

const modalContenidoTitle = computed(() => {
  if (modalContenidoMode.value === "draft") {
    const p = selectedCatalogProduct.value;
    return p ? t("ventas.pos.content_modal_title_draft", { name: p.name }) : t("ventas.pos.content_modal_title");
  }
  const parent = carrito.value.find((x) => x.uid === modalContenidoParentUid.value);
  return parent ? t("ventas.pos.content_modal_title_cart", { name: posLineLabel(parent) }) : t("ventas.pos.content_modal_title");
});

const modalContenidoSubtitle = computed(() =>
  modalContenidoMode.value === "draft" ? t("ventas.pos.content_modal_subtitle_draft") : t("ventas.pos.content_modal_subtitle_cart")
);

const catalogBillingIsWeight = computed(() => selectedCatalogProduct.value?.billing_unit === "kg");

const catalogProductConfigHint = computed(() => {
  const p = selectedCatalogProduct.value;
  if (!p) return "";
  const parts = [];
  parts.push(p.billing_unit === "kg" ? t("ventas.pos.billing_weight_catalog") : t("ventas.pos.billing_piece_unit"));
  const st = etapas.value.find((x) => x.id === p.default_etapa_id);
  if (st) parts.push(st.name);
  const lt = loadTypes.value.find((x) => x.id === p.default_load_type_id);
  if (lt) parts.push(lt.name);
  return parts.join(" · ");
});

const tibuLineConfigHint = computed(() => {
  if (!nuevaLinea.garment_type_id) return "";
  const g = garmentById(nuevaLinea.garment_type_id);
  const parts = [];
  parts.push(nuevaLinea.billing_mode === "by_weight" ? t("ventas.pos.billing_weight") : t("ventas.pos.billing_garment"));
  const lt = loadTypes.value.find((x) => x.id === nuevaLinea.load_type_id);
  if (lt) parts.push(lt.name);
  else if (g?.default_load_type_id) {
    const dlt = loadTypes.value.find((x) => x.id === g.default_load_type_id);
    if (dlt) parts.push(dlt.name);
  }
  return parts.join(" · ");
});

const nuevaLineaContenido = reactive({
  garment_type_id: null,
  cantidad: 1,
  observations: "",
});

const nuevaLinea = reactive({
  garment_type_id: null,
  load_type_id: null,
  billing_mode: "by_garment_type",
  cantidad: 1,
  weight_kg: null,
  observations: "",
});
const nuevaLineaDefaultState = reactive({
  loadApplied: false,
  billingApplied: false,
});

const form = reactive({
  cliente_nombre: "",
  cliente_email: "",
  detalles_ropa: "",
  instrucciones: "",
  mostrar_observaciones_en_ticket: true,
  is_delivery: false,
  delivery_address: "",
  save_client_direccion: false,
  fecha_entrega: "",
  tipo_servicio_id: null,
  semaphore_hours_red: 2,
  semaphore_hours_yellow: 3,
  etapa_id: null,
});

const expandedCartUid = ref(null);

const paymentLines = ref([{ metodo_pago: "efectivo", monto: 0 }]);

function round2(n) {
  return Math.round((Number(n) || 0) * 100) / 100;
}

function hydratePaymentsFromOrden(orden) {
  const t = round2(Number(orden?.total ?? 0));
  const pagos = Array.isArray(orden?.pagos) ? orden.pagos : [];
  if (pagos.length) {
    paymentLines.value = pagos.map((p) => ({
      metodo_pago: p.metodo_pago || "efectivo",
      monto: round2(p.monto),
    }));
    return;
  }
  const mp = orden?.metodo_pago;
  const method = mp && mp !== "mixto" ? mp : "efectivo";
  paymentLines.value = [{ metodo_pago: method, monto: t > 0 ? t : 0 }];
}

function addPaymentLine() {
  paymentLines.value.push({ metodo_pago: "efectivo", monto: 0 });
}

function removePaymentLine(idx) {
  if (paymentLines.value.length <= 1) return;
  paymentLines.value.splice(idx, 1);
}

function resolvePieceUnitPrice(etapaId, garmentTypeId, loadTypeId) {
  if (!etapaId || !garmentTypeId) return null;
  const rows = laundryPiecePrices.value.filter(
    (r) => r.etapa_id === etapaId && r.garment_type_id === garmentTypeId
  );
  const lid = loadTypeId != null && loadTypeId !== "" ? Number(loadTypeId) : null;
  if (lid != null) {
    const spec = rows.find((r) => r.load_type_id === lid);
    if (spec) return parseFloat(spec.unit_price);
  }
  const fallback = rows.find((r) => r.load_type_id == null);
  if (fallback != null) return parseFloat(fallback.unit_price);

  return null;
}

function saleProductById(id) {
  if (!id) return null;
  return saleProducts.value.find((p) => p.id === id) || null;
}

function catalogLineBillingLabel(it) {
  const p = saleProductById(it.laundry_sale_product_id);
  if (p?.billing_unit === "kg") return t("ventas.pos.billing_weight");
  return t("ventas.pos.billing_piece_unit");
}

function garmentName(garmentTypeId) {
  return garmentTypes.value.find((g) => g.id === garmentTypeId)?.name || "—";
}

function countContenidoForParent(parentUid) {
  return carrito.value.filter((it) => it.is_informational && it.parent_uid === parentUid).length;
}

function resetNuevaLineaContenido() {
  nuevaLineaContenido.garment_type_id = null;
  nuevaLineaContenido.cantidad = 1;
  nuevaLineaContenido.observations = "";
}

function openContenidoModalDraft() {
  if (!selectedCatalogProduct.value) return;
  modalContenidoMode.value = "draft";
  modalContenidoParentUid.value = null;
  resetNuevaLineaContenido();
  applyOtrosGarmentDefault();
  modalContenidoOpen.value = true;
}

function openContenidoModalCart(item) {
  modalContenidoMode.value = "cart";
  modalContenidoParentUid.value = item.uid;
  resetNuevaLineaContenido();
  modalContenidoOpen.value = true;
}

function closeContenidoModal() {
  modalContenidoOpen.value = false;
}

function pushContenidoEntry(targetList, productId) {
  targetList.push({
    uid: uid(),
    garment_type_id: nuevaLineaContenido.garment_type_id,
    cantidad: Math.max(1, parseInt(nuevaLineaContenido.cantidad, 10) || 1),
    observations: String(nuevaLineaContenido.observations || "").trim(),
    laundry_sale_product_id: productId,
  });
  resetNuevaLineaContenido();
}

function agregarContenidoEnModal() {
  if (addContenidoDisabled.value) return;
  if (modalContenidoMode.value === "draft") {
    const p = selectedCatalogProduct.value;
    if (!p) return;
    pushContenidoEntry(contenidoPendiente.value, p.id);
    return;
  }
  const parent = carrito.value.find((x) => x.uid === modalContenidoParentUid.value);
  if (!parent?.laundry_sale_product_id) return;
  const entry = {
    uid: uid(),
    is_informational: true,
    parent_uid: parent.uid,
    laundry_sale_product_id: parent.laundry_sale_product_id,
    garment_type_id: nuevaLineaContenido.garment_type_id,
    cantidad: Math.max(1, parseInt(nuevaLineaContenido.cantidad, 10) || 1),
    observations: String(nuevaLineaContenido.observations || "").trim(),
  };
  carrito.value.push(entry);
  resetNuevaLineaContenido();
  syncCartPreviewTotals();
}

function quitarContenidoEnModal(row) {
  if (modalContenidoMode.value === "draft") {
    contenidoPendiente.value = contenidoPendiente.value.filter((x) => x.uid !== row.uid);
    return;
  }
  quitarDelCarrito(row);
}

function posLineLabel(it) {
  if (it.is_informational) {
    const p = saleProductById(it.laundry_sale_product_id);
    const g = garmentTypes.value.find((x) => x.id === it.garment_type_id);
    return `${p?.name || "—"} → ${g?.name || "—"}`;
  }
  if (it.laundry_sale_product_id) {
    const p = saleProductById(it.laundry_sale_product_id);
    const s = etapas.value.find((x) => x.id === it.etapa_id);
    const lt = it.load_type_id != null ? loadTypes.value.find((x) => x.id === it.load_type_id) : null;
    const parts = [p?.name || "—", s?.name || ""].filter(Boolean);
    let label = parts.join(" · ");
    if (lt?.name) label += ` (${lt.name})`;
    return label;
  }
  return tibuLineLabel(it);
}

function lineUnitDisplay(it) {
  if (it.is_informational) return 0;
  const override = resolveCustomUnitPrice(it);
  if (override != null) return override;
  const preview = previewByUid.value[it.uid];
  if (preview?.precio_unitario != null) {
    return Number(preview.precio_unitario) || 0;
  }
  if (it.laundry_sale_product_id) {
    const p = saleProductById(it.laundry_sale_product_id);
    return p ? Number(p.unit_price) || 0 : 0;
  }
  if (it.billing_mode === "by_weight") {
    return pricePerKg.value != null ? pricePerKg.value : 0;
  }
  return resolvePieceUnitPrice(it.etapa_id, it.garment_type_id, it.load_type_id) ?? 0;
}

function billingQtyForLine(it) {
  if ((it.billing_mode || "") === "by_weight") {
    return billingWeightForLine(it);
  }
  const n = Number(it.cantidad);
  if (!Number.isFinite(n) || n <= 0) return 0;
  return Math.max(pieceMinBillQty.value, Math.round(n));
}

function billingQtyForCatalogDraft() {
  return billingPieceQuantity(nuevaLineaCatalog.cantidad, pieceMinBillQty.value);
}

function billingWeightForLine(it) {
  return normalizeWeightKg(it.weight_kg != null && it.weight_kg !== "" ? it.weight_kg : 0);
}

function formatRealWeight(it) {
  const w = Number(it.weight_kg);
  if (!Number.isFinite(w) || w <= 0) return "—";
  return w.toFixed(1).replace(/\.0$/, "");
}

function formatBillingWeight(it) {
  return billingWeightForLine(it).toFixed(1).replace(/\.0$/, "");
}

function formatBillingWeightDraft() {
  return normalizeWeightKg(nuevaLineaCatalog.weight_kg).toFixed(1).replace(/\.0$/, "");
}

function catalogDefaultUnitPrice(productId) {
  const p = saleProductById(productId);
  if (!p) return null;
  const u = Number(p.unit_price);
  return Number.isFinite(u) ? u : null;
}

function onCustomPriceToggle(item) {
  if (item.use_custom_price && (item.custom_unit_price == null || item.custom_unit_price === "")) {
    item.custom_unit_price = lineUnitDisplay(item);
  }
  syncCartPreviewTotals();
}

function resolveCustomUnitPrice(it) {
  if (!it?.use_custom_price) return null;
  const n = Number(it.custom_unit_price);
  return Number.isFinite(n) && n >= 0 ? n : null;
}

function appendCustomPricePayload(payload, it) {
  const override = resolveCustomUnitPrice(it);
  if (override != null) {
    payload.precio_unitario_override = override;
  }
}

function lineSubtotal(it) {
  if (it.is_informational) return 0;
  const preview = previewByUid.value[it.uid];
  if (preview?.subtotal != null) {
    return Number(preview.subtotal) || 0;
  }
  if (it.laundry_sale_product_id) {
    const p = saleProductById(it.laundry_sale_product_id);
    if (!p) return 0;
    const up = resolveCustomUnitPrice(it) ?? (Number(p.unit_price) || 0);
    if ((it.billing_mode || "") === "by_weight") {
      const w = billingWeightForLine(it);
      return w > 0 ? w * up : 0;
    }
    const c = billingQtyForLine(it);
    return c > 0 ? c * up : 0;
  }
  if (it.billing_mode === "by_weight") {
    const w = billingWeightForLine(it);
    const ppk = resolveCustomUnitPrice(it) ?? pricePerKg.value;
    if (!w || w <= 0 || ppk == null) return 0;
    return w * ppk;
  }
  const unit = resolveCustomUnitPrice(it) ?? resolvePieceUnitPrice(it.etapa_id, it.garment_type_id, it.load_type_id);
  if (unit == null) return 0;
  const c = billingQtyForLine(it);
  return c > 0 ? c * unit : 0;
}

function tibuLineLabel(it) {
  const g = garmentTypes.value.find((x) => x.id === it.garment_type_id);
  const s = etapas.value.find((x) => x.id === it.etapa_id);
  const lt = it.load_type_id != null ? loadTypes.value.find((x) => x.id === it.load_type_id) : null;
  const parts = [g?.name || "—", s?.name || ""].filter(Boolean);
  let label = parts.join(" · ");
  if (lt?.name) label += ` (${lt.name})`;
  return label;
}

function normalizeWeightKg(raw) {
  return roundLaundryWeightKg(raw);
}

function applyWeightToLine(it) {
  if ((it.billing_mode || "") !== "by_weight") return;
  const w = Number(it.weight_kg);
  if (!Number.isFinite(w) || w <= 0) {
    it.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
  }
}

function onWeightKgChange(it) {
  applyWeightToLine(it);
  syncCartPreviewTotals();
}


function tibuLinePriceWarning(it) {
  if (it.is_informational || it.laundry_sale_product_id) return "";
  if (it.billing_mode === "by_weight") {
    if (pricePerKg.value != null) return "";
    return t("ventas.pos.validation.no_price_per_kg");
  }
  if (!it.garment_type_id) return "";
  const u = resolvePieceUnitPrice(it.etapa_id, it.garment_type_id, it.load_type_id);
  return u == null ? t("ventas.pos.validation.no_piece_price") : "";
}

const clienteSeleccionadoId = computed(() => clienteSeleccionado.value?.id ?? null);

const bolsasListPos = computed(() => {
  const raw = ordenAbierta.value?.bolsas;
  return Array.isArray(raw) ? [...raw].sort((a, b) => (a.indice || 0) - (b.indice || 0)) : [];
});

const rules = computed(() => ({
  clienteSeleccionadoId: {
    required: helpers.withMessage(
      () => t("ventas.pos.validation.cliente_select_required"),
      () => !!clienteSeleccionado.value?.id
    ),
  },
  delivery_address: {
    required: helpers.withMessage(() => t("ventas.pos.validation.delivery_address"), requiredIf(() => form.is_delivery)),
  },
}));

const v$ = useVuelidate(rules, { clienteSeleccionadoId, delivery_address: computed(() => form.delivery_address) });

const totalCarrito = computed(() => {
  if (previewTotal.value != null) {
    return Number(previewTotal.value) || 0;
  }

  return carrito.value.reduce((sum, it) => sum + lineSubtotal(it), 0);
});

const sumaPagosIngresados = computed(() =>
  round2(paymentLines.value.reduce((s, pl) => s + (Number(pl.monto) || 0), 0))
);

const pagosSumanTotal = computed(() => {
  const t = round2(totalCarrito.value);
  if (t <= 0) return true;
  return Math.abs(sumaPagosIngresados.value - t) <= 0.02;
});

watchEffect(() => {
  const t = round2(totalCarrito.value);
  if (paymentLines.value.length === 1) {
    paymentLines.value[0].monto = t;
  }
});

const blockPaymentsForSubmit = computed(
  () => round2(totalCarrito.value) > 0 && paymentLines.value.length > 1 && !pagosSumanTotal.value
);

const serverErrors = ref({});
const creando = ref(false);
const isLoading = ref(false);
const ordenAbierta = ref(null);
const ordenGenerada = ref(null);
const mostrarModalOrden = ref(false);
const descargandoPdf = ref(false);
const descargandoPdfServer = ref(false);
const posQrPreview = ref("");
const folioReapertura = ref("");
const buscandoOrdenByFolio = ref(false);
const ACTIVE_ORDER_STORAGE_KEY = "tibu_pos_active_order_id";
const OPEN_ORDER_STORAGE_KEY = "tibu_pos_open_order_id";
const RECENT_OPEN_FOLIOS_STORAGE_KEY = "tibu_pos_recent_open_folios";
const foliosRecientes = ref([]);

watch(
  () => [mostrarModalOrden.value, ordenGenerada.value],
  async ([open, orden]) => {
    posQrPreview.value = "";
    if (open && orden?.folio_unico) {
      posQrPreview.value = await buildSeguimientoQrDataUrl(orden, { width: 200 });
    }
  },
  { immediate: true }
);

/** Preferencia §21: abrir ticket jsPDF (2× QR) justo después de registrar la venta. */
const AUTO_TICKET_STORAGE_KEY = "tibu_pos_auto_imprimir_ticket";

function readAutoImprimirTicket() {
  try {
    return localStorage.getItem(AUTO_TICKET_STORAGE_KEY) !== "0";
  } catch {
    return true;
  }
}

function writeAutoImprimirTicket(value) {
  try {
    localStorage.setItem(AUTO_TICKET_STORAGE_KEY, value ? "1" : "0");
  } catch {
    /* ignore */
  }
}

const autoImprimirTicket = ref(true);
watch(autoImprimirTicket, (v) => writeAutoImprimirTicket(v));

let timeoutBusqueda = null;
let timeoutPreview = null;

function uid() {
  return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
}

function garmentById(id) {
  if (!id) return null;
  return garmentTypes.value.find((g) => g.id === id) || null;
}

function garmentOtrosId() {
  return garmentTypes.value.find((g) => String(g.code || "").toLowerCase() === "otros")?.id ?? null;
}

function isOtrosProduct(p) {
  if (!p) return false;
  const code = String(p.code || "").toLowerCase();
  const name = String(p.name || "").trim().toLowerCase();
  return code === "199" || code === "otros" || name === "otros";
}

function ensureOtrosContenidoPendiente(product) {
  if (!isOtrosProduct(product)) return;
  const gid = garmentOtrosId();
  if (!gid) return;
  if (contenidoPendiente.value.some((row) => Number(row.garment_type_id) === Number(gid))) return;
  contenidoPendiente.value.push({
    uid: uid(),
    garment_type_id: gid,
    cantidad: Math.max(1, parseInt(nuevaLineaCatalog.cantidad, 10) || 1),
    observations: String(nuevaLineaCatalog.observations || "").trim(),
    laundry_sale_product_id: product.id,
  });
}

function applyOtrosGarmentDefault() {
  const p = selectedCatalogProduct.value;
  if (!isOtrosProduct(p)) return;
  const gid = garmentOtrosId();
  if (gid) {
    nuevaLineaContenido.garment_type_id = gid;
  }
  ensureOtrosContenidoPendiente(p);
}

function applyGarmentDefaultsToLine(targetLine, garmentTypeId) {
  const garment = garmentById(garmentTypeId);
  if (!garment) {
    return { loadApplied: false, billingApplied: false };
  }
  let loadApplied = false;
  let billingApplied = false;

  if ((targetLine.load_type_id == null || targetLine.load_type_id === "") && garment.default_load_type_id != null) {
    targetLine.load_type_id = Number(garment.default_load_type_id);
    loadApplied = true;
  }

  return { loadApplied, billingApplied };
}

function onNuevaLineaGarmentChange() {
  const fallbackBilling = nuevaLinea.billing_mode;
  nuevaLinea.billing_mode = "";
  const applied = applyGarmentDefaultsToLine(nuevaLinea, nuevaLinea.garment_type_id);
  if (!nuevaLinea.billing_mode) {
    nuevaLinea.billing_mode = fallbackBilling || "by_garment_type";
  }
  nuevaLineaDefaultState.loadApplied = applied.loadApplied;
  nuevaLineaDefaultState.billingApplied = applied.billingApplied;
}

function onNuevaLineaLoadOverride() {
  nuevaLineaDefaultState.loadApplied = false;
}

function onNuevaLineaBillingOverride() {
  nuevaLineaDefaultState.billingApplied = false;
}

function onCartGarmentChange(item) {
  const fallbackBilling = item.billing_mode;
  item.billing_mode = "";
  applyGarmentDefaultsToLine(item, item.garment_type_id);
  if (!item.billing_mode) {
    item.billing_mode = fallbackBilling || "by_garment_type";
  }
  syncCartPreviewTotals();
}

async function cargarCaja() {
  cajaCargada.value = false;
  try {
    const res = await CajaRepository.getEstado();
    cajaAbierta.value = !!(res && res.data);
  } catch {
    cajaAbierta.value = false;
  }
  cajaCargada.value = true;
}

async function cargarCategoriasClientesPos() {
  try {
    const body = await ClienteRepository.getClientes({ per_page: 1, page: 1 });
    categoriasClientes.value = Array.isArray(body?.meta?.categorias) ? body.meta.categorias : [];
  } catch {
    categoriasClientes.value = [];
  }
}

async function cargarCatalogosPos() {
  cargandoCatalogoPos.value = true;
  try {
    const body = await VentasRepository.getPosCatalog();
    const d = body?.data || body;
    tiposServicio.value = d?.tipos_servicio || [];
    etapas.value = d?.etapas || [];
    garmentTypes.value = d?.garment_types || [];
    loadTypes.value = d?.load_types || [];
    saleCategories.value = Array.isArray(d?.laundry_sale_categories) ? d.laundry_sale_categories : [];
    saleProducts.value = Array.isArray(d?.laundry_sale_products) ? d.laundry_sale_products : [];
    laundryPiecePrices.value = Array.isArray(d?.laundry_piece_prices) ? d.laundry_piece_prices : [];
    const ppk = d?.price_per_kg;
    pricePerKg.value = ppk != null && ppk !== "" ? parseFloat(ppk) : null;
    defaultDeliveryDays.value = Number(d?.default_delivery_days ?? 3) || 3;
    defaultDeliveryHour.value = Number(d?.default_delivery_hour ?? 20) || 20;
    defaultSemaphoreRed.value = Number(d?.semaphore_hours_red ?? 2) || 0;
    defaultSemaphoreYellow.value = Number(d?.semaphore_hours_yellow ?? 3) || 0;
    pieceMinBillQty.value = Number(d?.piece_min_bill_qty ?? LAUNDRY_PIECE_MIN_BILL_QTY) || LAUNDRY_PIECE_MIN_BILL_QTY;
    receptionPrinterConfigured.value = !!d?.reception_printer?.configured;

    // Default POS: "Servicio completo" (se basa en code en el catálogo).
    defaultEtapaId.value = etapas.value.find((st) => st.code === "completo")?.id ?? etapas.value[0]?.id ?? null;
    if (!form.fecha_entrega && !ordenAbierta.value?.id) {
      form.fecha_entrega = defaultFechaEntregaLocal({
        days: defaultDeliveryDays.value,
        hour: defaultDeliveryHour.value,
      });
    }
    if (form.tipo_servicio_id == null && !ordenAbierta.value?.id) {
      form.tipo_servicio_id = defaultTipoServicioId();
    }
    if (!ordenAbierta.value?.id) {
      applyDefaultSemaphoreHours();
    }
  } catch {
    tiposServicio.value = [];
    etapas.value = [];
    defaultEtapaId.value = null;
    garmentTypes.value = [];
    loadTypes.value = [];
    saleCategories.value = [];
    saleProducts.value = [];
    laundryPiecePrices.value = [];
    pricePerKg.value = null;
    defaultDeliveryDays.value = 3;
    defaultDeliveryHour.value = 20;
    defaultSemaphoreRed.value = 2;
    defaultSemaphoreYellow.value = 3;
    receptionPrinterConfigured.value = false;
  } finally {
    cargandoCatalogoPos.value = false;
  }
}

const previewNuevaLineaUnit = computed(() => {
  if (!form.etapa_id) return null;
  if (nuevaLinea.billing_mode === "by_weight") {
    return pricePerKg.value != null ? pricePerKg.value : null;
  }
  if (!nuevaLinea.garment_type_id) return null;
  return resolvePieceUnitPrice(form.etapa_id, nuevaLinea.garment_type_id, nuevaLinea.load_type_id);
});

const previewNuevaLineaSub = computed(() => {
  if (previewNuevaLineaUnit.value == null) return 0;
  if (nuevaLinea.billing_mode === "by_weight") {
    const w = parseFloat(nuevaLinea.weight_kg);
    if (!w || w <= 0) return 0;
    return w * previewNuevaLineaUnit.value;
  }
  const c = Math.max(1, parseInt(nuevaLinea.cantidad, 10) || 1);
  return c * previewNuevaLineaUnit.value;
});

const previewNuevaLineaError = computed(() => {
  if (!form.etapa_id) return "";
  if (nuevaLinea.billing_mode === "by_weight") {
    if (pricePerKg.value != null) return "";
    return t("ventas.pos.validation.no_price_per_kg");
  }
  if (!nuevaLinea.garment_type_id) return "";
  const u = resolvePieceUnitPrice(form.etapa_id, nuevaLinea.garment_type_id, nuevaLinea.load_type_id);
  return u == null ? t("ventas.pos.validation.no_piece_price") : "";
});

const previewNuevaLineaCatalogUnit = computed(() => {
  if (nuevaLineaCatalog.use_custom_price) {
    const n = Number(nuevaLineaCatalog.custom_unit_price);
    return Number.isFinite(n) ? Math.max(0, n) : null;
  }
  const p = saleProductById(nuevaLineaCatalog.laundry_sale_product_id);
  if (!p) return null;
  const u = Number(p.unit_price);
  return Number.isFinite(u) ? u : null;
});

const previewNuevaLineaCatalogSub = computed(() => {
  const u = previewNuevaLineaCatalogUnit.value;
  if (u == null) return 0;
  if (nuevaLineaCatalog.billing_mode === "by_weight") {
    const w = normalizeWeightKg(nuevaLineaCatalog.weight_kg);
    if (!w || w <= 0) return 0;
    return w * u;
  }
  const c = billingQtyForCatalogDraft();
  return c > 0 ? c * u : 0;
});

const previewNuevaLineaCatalogError = computed(() => {
  const p = saleProductById(nuevaLineaCatalog.laundry_sale_product_id);
  if (!p) return "";
  if (!p.default_etapa_id) return t("ventas.pos.validation.catalog_service_required");
  if (nuevaLineaCatalog.billing_mode === "by_weight") {
    const w = parseFloat(nuevaLineaCatalog.weight_kg);
    if (!w || w <= 0) return t("ventas.pos.validation.weight_required");
  } else {
    const c = Math.max(0, parseInt(nuevaLineaCatalog.cantidad, 10) || 0);
    if (c < 1) return t("ventas.pos.validation.catalog_qty_required");
  }
  return "";
});

const addCatalogDisabled = computed(() => {
  if (!cajaAbierta.value) return true;
  const p = saleProductById(nuevaLineaCatalog.laundry_sale_product_id);
  if (!p) return true;
  if (!p.default_etapa_id) return true;
  if (previewNuevaLineaCatalogError.value) return true;
  if (previewNuevaLineaCatalogUnit.value == null) return true;
  return false;
});

const addContenidoDisabled = computed(() => {
  if (!selectedCatalogProduct.value) return true;
  if (!nuevaLineaContenido.garment_type_id) return true;
  const c = Math.max(1, parseInt(nuevaLineaContenido.cantidad, 10) || 1);
  return c < 1;
});

const hasCartPriceBlock = computed(() =>
  carrito.value.some((it) => !it.laundry_sale_product_id && !it.is_informational && tibuLinePriceWarning(it))
);

watch(
  carrito,
  () => {
    if (timeoutPreview) clearTimeout(timeoutPreview);
    timeoutPreview = setTimeout(syncCartPreviewTotals, 200);
  },
  { deep: true }
);

watch(
  () => nuevaLinea.billing_mode,
  (mode) => {
    if (mode === "by_weight") {
      if (nuevaLinea.weight_kg == null || nuevaLinea.weight_kg <= 0) nuevaLinea.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
    } else {
      nuevaLinea.cantidad = Math.max(1, parseInt(nuevaLinea.cantidad, 10) || 1);
    }
  }
);

watch(
  () => nuevaLineaCatalog.laundry_sale_product_id,
  (id, prevId) => {
    if (prevId != null && id !== prevId) {
      contenidoPendiente.value = [];
    }
    if (!id) {
      contenidoPendiente.value = [];
      return;
    }
    const p = saleProductById(id);
    if (!p) return;
    nuevaLineaCatalog.billing_mode = p.billing_unit === "kg" ? "by_weight" : "by_garment_type";
    nuevaLineaCatalog.load_type_id = p.default_load_type_id ?? null;
    nuevaLineaCatalog.etapa_id = null;
    nuevaLineaCatalog.use_custom_price = false;
    nuevaLineaCatalog.custom_unit_price = catalogDefaultUnitPrice(id);
    if (nuevaLineaCatalog.billing_mode === "by_weight") {
      if (nuevaLineaCatalog.weight_kg == null || nuevaLineaCatalog.weight_kg <= 0) nuevaLineaCatalog.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
    } else {
      nuevaLineaCatalog.cantidad = Math.max(1, parseInt(nuevaLineaCatalog.cantidad, 10) || 1);
    }
  }
);

watch(
  () => nuevaLineaCatalog.use_custom_price,
  (enabled) => {
    if (enabled && (nuevaLineaCatalog.custom_unit_price == null || nuevaLineaCatalog.custom_unit_price === "")) {
      nuevaLineaCatalog.custom_unit_price = catalogDefaultUnitPrice(nuevaLineaCatalog.laundry_sale_product_id);
    }
  }
);

watch(
  () => nuevaLineaCatalog.billing_mode,
  (mode) => {
    if (mode === "by_weight") {
      if (nuevaLineaCatalog.weight_kg == null || nuevaLineaCatalog.weight_kg <= 0) nuevaLineaCatalog.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
    } else {
      nuevaLineaCatalog.cantidad = Math.max(1, parseInt(nuevaLineaCatalog.cantidad, 10) || 1);
    }
  }
);

function clienteCategoriaBadgeStyle(cat) {
  if (!cat?.color) return { backgroundColor: "#6c757d", color: "#fff" };
  return { backgroundColor: cat.color, color: "#fff" };
}

function buscarClientes() {
  clearTimeout(timeoutBusqueda);
  const q = busquedaCliente.value.trim();
  if (q.length < 2) {
    sugerenciasClientes.value = [];
    return;
  }
  timeoutBusqueda = setTimeout(async () => {
    try {
      const body = await ClienteRepository.getClientes({ q, per_page: 40, page: 1 });
      const p = body?.data;
      sugerenciasClientes.value = Array.isArray(p?.data) ? p.data : [];
      if (Array.isArray(body?.meta?.categorias) && body.meta.categorias.length) {
        categoriasClientes.value = body.meta.categorias;
      }
    } catch {
      sugerenciasClientes.value = [];
    }
  }, 300);
}

function seleccionarCliente(c) {
  clienteSeleccionado.value = c;
  form.cliente_nombre = c.nombre;
  form.cliente_email = c.email || "";
  if (form.is_delivery) {
    applyDeliveryAddressFromCliente();
  }
  busquedaCliente.value = "";
  sugerenciasClientes.value = [];
  mostrarSugerencias.value = false;
}

function applyDeliveryAddressFromCliente() {
  const addr = clienteSeleccionado.value?.direccion?.trim();
  if (addr) {
    form.delivery_address = addr;
  }
}

async function maybeUpdateClienteDireccion() {
  if (!form.is_delivery || !form.save_client_direccion) return;
  const c = clienteSeleccionado.value;
  if (!c?.id || c.es_mostrador) return;
  const addr = form.delivery_address?.trim() || null;
  if (addr === (c.direccion?.trim() || null)) return;
  try {
    const res = await ClienteRepository.updateCliente(c.id, { direccion: addr });
    const updated = res?.data ?? res;
    if (updated?.id) {
      clienteSeleccionado.value = { ...clienteSeleccionado.value, direccion: updated.direccion ?? addr };
    }
  } catch (e) {
    showMessage(
      t("ventas.pos.client_address_save_error", { msg: e?.response?.data?.message || e.message }),
      "warning"
    );
  }
}

watch(
  () => form.is_delivery,
  (enabled, prev) => {
    if (enabled && !prev) {
      applyDeliveryAddressFromCliente();
    }
    if (!enabled) {
      form.save_client_direccion = false;
    }
  }
);

function quitarCliente() {
  clienteSeleccionado.value = null;
  form.cliente_nombre = "";
  form.cliente_email = "";
}

function resetCatalogDraft() {
  contenidoPendiente.value = [];
  nuevaLineaCatalog.category_id = null;
  nuevaLineaCatalog.laundry_sale_product_id = null;
  nuevaLineaCatalog.etapa_id = null;
  nuevaLineaCatalog.load_type_id = null;
  nuevaLineaCatalog.billing_mode = "by_garment_type";
  nuevaLineaCatalog.cantidad = 1;
  nuevaLineaCatalog.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
  nuevaLineaCatalog.use_custom_price = false;
  nuevaLineaCatalog.custom_unit_price = null;
  nuevaLineaCatalog.observations = "";
}

function openCatalogModal() {
  if (!cajaAbierta.value || !saleProducts.value.length) return;
  resetCatalogDraft();
  catalogModalStep.value = "categories";
  modalCatalogOpen.value = true;
}

function closeCatalogModal() {
  modalCatalogOpen.value = false;
  catalogModalStep.value = "categories";
  resetCatalogDraft();
}

function pickCatalogCategory(cat) {
  nuevaLineaCatalog.category_id = cat.id;
  nuevaLineaCatalog.laundry_sale_product_id = null;
  contenidoPendiente.value = [];
  catalogModalStep.value = "services";
}

function pickCatalogService(p) {
  nuevaLineaCatalog.laundry_sale_product_id = p.id;
  catalogModalStep.value = "configure";
  applyOtrosGarmentDefault();
}

function catalogModalBack() {
  if (catalogModalStep.value === "configure") {
    nuevaLineaCatalog.laundry_sale_product_id = null;
    contenidoPendiente.value = [];
    catalogModalStep.value = "services";
  } else if (catalogModalStep.value === "services") {
    nuevaLineaCatalog.category_id = null;
    catalogModalStep.value = "categories";
  }
}

function confirmarCatalogoModal() {
  if (addCatalogDisabled.value) return;
  const p = selectedCatalogProduct.value;
  if (isOtrosProduct(p)) {
    ensureOtrosContenidoPendiente(p);
  }
  const lenBefore = carrito.value.length;
  agregarLineaCatalogo();
  if (carrito.value.length > lenBefore) {
    if (p?.name) {
      showMessage(t("ventas.pos.service_added_to_cart", { name: p.name }), "success");
    }
    closeCatalogModal();
  }
}

function abrirModalNuevoCliente() {
  nuevoCliente.value = {
    nombre: busquedaCliente.value || "",
    email: "",
    telefono: "",
    direccion: "",
    cliente_categoria_id: null,
  };
  modalNuevoCliente.value = true;
}

async function guardarNuevoCliente() {
  if (!nuevoCliente.value.nombre) return;
  guardandoCliente.value = true;
  try {
    const res = await ClienteRepository.createCliente(nuevoCliente.value);
    const data = res.data || res;
    if (data) {
      seleccionarCliente(data);
      modalNuevoCliente.value = false;
      showMessage(t("ventas.pos.client_saved"), "success");
    }
  } catch (e) {
    showMessage(t("ventas.pos.client_error", { msg: e.response?.data?.message || e.message }), "error");
  } finally {
    guardandoCliente.value = false;
  }
}

function agregarLineaCatalogo() {
  if (addCatalogDisabled.value) return;
  const p = saleProductById(nuevaLineaCatalog.laundry_sale_product_id);
  if (!p) return;
  const st = Number(p.default_etapa_id);
  if (!st) {
    showMessage(t("ventas.pos.validation.catalog_service_required"), "warning");
    return;
  }
  const bm = p.billing_unit === "kg" ? "by_weight" : "by_garment_type";
  const parentUid = uid();
  const line = {
    uid: parentUid,
    laundry_sale_product_id: p.id,
    etapa_id: st,
    load_type_id: p.default_load_type_id ?? null,
    billing_mode: bm,
    cantidad: bm === "by_garment_type" ? Math.max(1, parseInt(nuevaLineaCatalog.cantidad, 10) || 1) : 1,
    weight_kg: bm === "by_weight" ? Number(nuevaLineaCatalog.weight_kg) || LAUNDRY_WEIGHT_MIN_KG : null,
    use_custom_price: !!nuevaLineaCatalog.use_custom_price,
    custom_unit_price: nuevaLineaCatalog.use_custom_price ? Number(nuevaLineaCatalog.custom_unit_price) : null,
    observations: String(nuevaLineaCatalog.observations || "").trim(),
  };
  carrito.value.push(line);
  for (const row of contenidoPendiente.value) {
    carrito.value.push({
      uid: uid(),
      is_informational: true,
      parent_uid: parentUid,
      laundry_sale_product_id: p.id,
      garment_type_id: row.garment_type_id,
      cantidad: row.cantidad,
      observations: row.observations || "",
    });
  }
  contenidoPendiente.value = [];
  nuevaLineaCatalog.laundry_sale_product_id = null;
  nuevaLineaCatalog.etapa_id = null;
  nuevaLineaCatalog.load_type_id = null;
  nuevaLineaCatalog.category_id = null;
  nuevaLineaCatalog.billing_mode = "by_garment_type";
  nuevaLineaCatalog.cantidad = 1;
  nuevaLineaCatalog.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
  nuevaLineaCatalog.use_custom_price = false;
  nuevaLineaCatalog.custom_unit_price = null;
  nuevaLineaCatalog.observations = "";
  syncCartPreviewTotals();
}

function onTibuCantidadChange(item) {
  let n = Math.round(Number(item.cantidad));
  if (Number.isNaN(n) || n < 1) n = 1;
  item.cantidad = n;
  syncCartPreviewTotals();
}

function onLineBillingChange(item) {
  if (item.billing_mode === "by_weight") {
    if (item.weight_kg == null || item.weight_kg <= 0) item.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
    else applyWeightToLine(item);
    item.cantidad = 1;
  } else {
    item.cantidad = Math.max(1, parseInt(item.cantidad, 10) || 1);
    item.weight_kg = null;
  }
  syncCartPreviewTotals();
}

function quitarDelCarrito(itemOrIdx) {
  if (typeof itemOrIdx === "number") {
    const removed = carrito.value[itemOrIdx];
    if (removed?.uid) {
      carrito.value = carrito.value.filter((x) => x.uid !== removed.uid && x.parent_uid !== removed.uid);
    } else {
      carrito.value.splice(itemOrIdx, 1);
    }
    syncCartPreviewTotals();
    return;
  }
  const targetUid = itemOrIdx.uid;
  carrito.value = carrito.value.filter((x) => x.uid !== targetUid && x.parent_uid !== targetUid);
  syncCartPreviewTotals();
}

function resetForm() {
  form.cliente_nombre = "";
  form.cliente_email = "";
  form.detalles_ropa = "";
  form.instrucciones = "";
  form.mostrar_observaciones_en_ticket = true;
  form.is_delivery = false;
  form.delivery_address = "";
  form.save_client_direccion = false;
  form.fecha_entrega = defaultFechaEntregaLocal({
    days: defaultDeliveryDays.value,
    hour: defaultDeliveryHour.value,
  });
  form.tipo_servicio_id = defaultTipoServicioId();
  applyDefaultSemaphoreHours();
  expandedCartUid.value = null;
  nuevaLinea.garment_type_id = null;
  nuevaLinea.load_type_id = null;
  nuevaLinea.billing_mode = "by_garment_type";
  nuevaLinea.cantidad = 1;
  nuevaLinea.weight_kg = null;
  nuevaLinea.observations = "";
  nuevaLineaCatalog.category_id = null;
  nuevaLineaCatalog.laundry_sale_product_id = null;
  nuevaLineaCatalog.etapa_id = null;
  nuevaLineaCatalog.load_type_id = null;
  nuevaLineaCatalog.billing_mode = "by_garment_type";
  nuevaLineaCatalog.cantidad = 1;
  nuevaLineaCatalog.weight_kg = LAUNDRY_WEIGHT_MIN_KG;
  nuevaLineaCatalog.observations = "";
  clienteSeleccionado.value = null;
  busquedaCliente.value = "";
  bolsasCantidadDraft.value = 0;
  contenidoPendiente.value = [];
  modalContenidoOpen.value = false;
  carrito.value = [];
  previewByUid.value = {};
  previewTotal.value = null;
  serverErrors.value = {};
  paymentLines.value = [{ metodo_pago: "efectivo", monto: 0 }];
  v$.value.$reset();
}

function showMessage(msg, type) {
  const toast = window.Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true });
  toast.fire({ icon: type, title: msg });
}

function validateLineItems() {
  for (let i = 0; i < carrito.value.length; i++) {
    const it = carrito.value[i];
    if (it.is_informational) {
      if (!it.laundry_sale_product_id || !it.garment_type_id) {
        showMessage(t("ventas.pos.validation.content_line_required"), "warning");
        return false;
      }
      if (!it.cantidad || Number(it.cantidad) < 1) {
        showMessage(t("ventas.pos.validation.catalog_qty_required"), "warning");
        return false;
      }
      continue;
    }
    if (it.laundry_sale_product_id) {
      const p = saleProductById(it.laundry_sale_product_id);
      if (!p) {
        showMessage(t("ventas.pos.validation.catalog_product_missing"), "warning");
        return false;
      }
      if (!p?.default_etapa_id && !it.etapa_id) {
        showMessage(t("ventas.pos.validation.catalog_service_required"), "warning");
        return false;
      }
      if (!it.etapa_id && p?.default_etapa_id) {
        it.etapa_id = p.default_etapa_id;
      }
      if (it.billing_mode === "by_weight") {
        const w = normalizeWeightKg(it.weight_kg != null && it.weight_kg !== "" ? it.weight_kg : 0);
        it.weight_kg = w;
        if (!w || w <= 0) {
          showMessage(t("ventas.pos.validation.weight_required"), "warning");
          return false;
        }
      } else if (!it.cantidad || Number(it.cantidad) < 1) {
        showMessage(t("ventas.pos.validation.catalog_qty_required"), "warning");
        return false;
      }
      continue;
    }
    if (!it.etapa_id) {
      showMessage(t("ventas.pos.validation.service_type_required"), "warning");
      return false;
    }
    if (it.billing_mode === "by_garment_type") {
      if (!it.garment_type_id) {
        showMessage(t("ventas.pos.validation.garment_required"), "warning");
        return false;
      }
      const u = resolvePieceUnitPrice(it.etapa_id, it.garment_type_id, it.load_type_id);
      if (u == null) {
        showMessage(t("ventas.pos.validation.no_piece_price"), "warning");
        return false;
      }
    } else if (it.billing_mode === "by_weight") {
      const w = normalizeWeightKg(it.weight_kg != null && it.weight_kg !== "" ? it.weight_kg : 0);
      it.weight_kg = w;
      if (!w || w <= 0) {
        showMessage(t("ventas.pos.validation.weight_required"), "warning");
        return false;
      }
      if (pricePerKg.value == null) {
        showMessage(t("ventas.pos.validation.no_price_per_kg"), "warning");
        return false;
      }
    }
  }
  return true;
}

function buildItemsPayload() {
  return carrito.value.map((it) => {
    if (it.is_informational) {
      const payload = {
        is_informational: true,
        laundry_sale_product_id: it.laundry_sale_product_id,
        garment_type_id: it.garment_type_id,
        cantidad: Number(it.cantidad) || 1,
      };
      if (it.observations && String(it.observations).trim()) {
        payload.observations = String(it.observations).trim();
      }
      return payload;
    }
    if (it.laundry_sale_product_id) {
      const billing = it.billing_mode || "by_garment_type";
      const payload = {
        laundry_sale_product_id: it.laundry_sale_product_id,
        etapa_id: it.etapa_id,
        billing_mode: billing,
      };
      if (billing === "by_garment_type") {
        payload.cantidad = Number(it.cantidad) || 1;
      } else {
        payload.weight_kg = Number(it.weight_kg) || LAUNDRY_WEIGHT_MIN_KG;
        payload.cantidad = Number(it.weight_kg) || LAUNDRY_WEIGHT_MIN_KG;
      }
      if (it.load_type_id) payload.load_type_id = it.load_type_id;
      appendCustomPricePayload(payload, it);
      if (it.observations && String(it.observations).trim()) {
        payload.observations = String(it.observations).trim();
      }
      return payload;
    }
    const billing = it.billing_mode || "by_garment_type";
    const payload = {
      etapa_id: it.etapa_id,
      billing_mode: billing,
    };
    if (billing === "by_garment_type") {
      payload.garment_type_id = it.garment_type_id;
      payload.cantidad = Number(it.cantidad) || 1;
    } else {
      payload.weight_kg = Number(it.weight_kg) || LAUNDRY_WEIGHT_MIN_KG;
      payload.cantidad = Number(it.weight_kg) || LAUNDRY_WEIGHT_MIN_KG;
    }
    if (it.load_type_id) payload.load_type_id = it.load_type_id;
    appendCustomPricePayload(payload, it);
    if (it.observations && String(it.observations).trim()) {
      payload.observations = String(it.observations).trim();
    }
    return payload;
  });
}

async function syncCartPreviewTotals() {
  if (carrito.value.length === 0) {
    previewByUid.value = {};
    previewTotal.value = null;

    return;
  }

  try {
    const body = await VentasRepository.postPosPreviewTotals({ items: buildItemsPayload() });
    const data = body?.data || {};
    const lines = Array.isArray(data?.lines) ? data.lines : [];
    const map = {};

    for (const line of lines) {
      const idx = Number(line?.index);
      if (Number.isInteger(idx) && idx >= 0 && idx < carrito.value.length) {
        const uid = carrito.value[idx]?.uid;
        if (uid) {
          map[uid] = line;
        }
      }
    }

    previewByUid.value = map;
    previewTotal.value = data?.total != null ? Number(data.total) : null;
  } catch {
    // Fallback local (matriz del catálogo) si falla preview API.
    previewByUid.value = {};
    previewTotal.value = null;
  }
}

async function crearOrden() {
  await guardarOrdenAbierta({ finalizar: false });
}

async function finalizarOrdenAbierta() {
  const orden = await guardarOrdenAbierta({ finalizar: true });
  if (!orden) return;
  ordenGenerada.value = orden;
  mostrarModalOrden.value = true;
  const finalizedId = orden?.id;
  await nuevaOrdenVacia();
  if (readAutoImprimirTicket()) {
    await abrirTicketDosQrConOrden(orden);
  }
}

function syncBolsasDraftFromOrden(orden) {
  const list = Array.isArray(orden?.bolsas) ? orden.bolsas : [];
  bolsasCantidadDraft.value = list.length;
}

async function syncBolsasOrden(ordenId, cantidad, { silent = false } = {}) {
  let n = Number(cantidad);
  if (!Number.isFinite(n) || n < 0) n = 0;
  n = Math.min(500, Math.max(0, Math.floor(n)));

  aplicandoBolsas.value = true;
  try {
    const response = await OrdenLavanderiaRepository.syncBolsas(ordenId, n);
    const orden = response.data;
    if (orden && ordenAbierta.value?.id === ordenId) {
      ordenAbierta.value = orden;
      syncBolsasDraftFromOrden(orden);
    }
    if (orden && ordenGenerada.value?.id === ordenId) {
      ordenGenerada.value = orden;
    }
    if (!silent) {
      showMessage(t("ventas.pos.bags_updated"), "success");
    }
    return orden;
  } catch (error) {
    const msg =
      error.response?.data?.errors?.orden?.[0] ||
      error.response?.data?.message ||
      t("ventas.pos.bags_error");
    showMessage(msg, "error");
    return null;
  } finally {
    aplicandoBolsas.value = false;
  }
}

async function abrirPdfBolsas(ordenId, indice) {
  imprimiendoBolsasPdf.value = indice == null ? "all" : indice;
  try {
    const res = await OrdenLavanderiaRepository.fetchBolsasPdfBlob(ordenId, {
      indice: indice == null ? undefined : indice,
    });
    const ct = String(res.headers["content-type"] || "");
    if (!ct.includes("pdf")) {
      let msg = t("ventas.pos.bags_pdf_error");
      try {
        const text = await res.data.text();
        const j = JSON.parse(text);
        if (j.message) msg = j.message;
      } catch {
        /* ignore */
      }
      showMessage(msg, "error");
      return false;
    }
    const url = URL.createObjectURL(new Blob([res.data], { type: "application/pdf" }));
    window.open(url, "_blank");
    return true;
  } catch {
    showMessage(t("ventas.pos.bags_pdf_error"), "error");
    return false;
  } finally {
    imprimiendoBolsasPdf.value = null;
  }
}

async function ensureBolsasMinimas(ordenId, minCount = 1) {
  const ordenActual =
    ordenAbierta.value?.id === ordenId
      ? ordenAbierta.value
      : ordenGenerada.value?.id === ordenId
        ? ordenGenerada.value
        : null;
  const actuales = Array.isArray(ordenActual?.bolsas) ? ordenActual.bolsas.length : 0;
  if (actuales >= minCount) return true;
  const orden = await syncBolsasOrden(ordenId, minCount, { silent: true });
  const total = Array.isArray(orden?.bolsas) ? orden.bolsas.length : 0;
  return total >= minCount;
}

async function enviarEtiquetaBolsaTermica(ordenId, { indice = null, todas = false } = {}) {
  if (!asegurarImpresoraRecepcion()) return false;
  const trackingKey = todas ? "all" : indice ?? "quick";
  imprimiendoBolsasPdf.value = trackingKey;
  try {
    const body = await OrdenLavanderiaRepository.imprimirEtiquetaBolsaTermica(ordenId, {
      posReception: true,
      indice: todas ? undefined : indice ?? 1,
      todas,
    });
    if (body?.success || body?.impresion?.success) {
      showMessage(body.message || body.impresion?.message || t("ventas.pos.bags_thermal_ok"), "success");
      return true;
    }
    const msg = body?.impresion?.error || body?.message || t("ventas.pos.bags_thermal_error");
    showMessage(msg, "warning");
    return false;
  } catch (error) {
    const msg =
      error.response?.data?.impresion?.error ||
      error.response?.data?.message ||
      t("ventas.pos.bags_thermal_error");
    showMessage(msg, "warning");
    return false;
  } finally {
    imprimiendoBolsasPdf.value = null;
  }
}

async function imprimirEtiquetaBolsaRapida() {
  if (!ordenAbierta.value?.id) {
    showMessage(t("ventas.pos.bags_save_order_first"), "warning");
    return;
  }
  if (!clienteSeleccionado.value?.id) {
    showMessage(t("ventas.pos.validation.cliente_select_required"), "warning");
    return;
  }
  const ordenId = ordenAbierta.value.id;
  if (!(await ensureBolsasMinimas(ordenId, 1))) {
    showMessage(t("ventas.pos.bags_print_none"), "warning");
    return;
  }
  await enviarEtiquetaBolsaTermica(ordenId, { indice: 1 });
}

async function imprimirEtiquetaBolsaOrdenGenerada() {
  const orden = ordenGenerada.value;
  if (!orden?.id) return;
  if (!(await ensureBolsasMinimas(orden.id, 1))) {
    showMessage(t("ventas.pos.bags_print_none"), "warning");
    return;
  }
  await enviarEtiquetaBolsaTermica(orden.id, { indice: 1 });
}

async function aplicarCantidadBolsasPos() {
  if (!ordenAbierta.value?.id) {
    showMessage(t("ventas.pos.bags_save_order_first"), "warning");
    return;
  }
  if (!clienteSeleccionado.value?.id) {
    showMessage(t("ventas.pos.validation.cliente_select_required"), "warning");
    return;
  }
  await syncBolsasOrden(ordenAbierta.value.id, bolsasCantidadDraft.value);
}

async function imprimirPdfBolsasPos(indice) {
  if (!ordenAbierta.value?.id) return;
  if (!bolsasListPos.value.length) {
    showMessage(t("ventas.pos.bags_print_none"), "warning");
    return;
  }
  if (indice == null) {
    await enviarEtiquetaBolsaTermica(ordenAbierta.value.id, { todas: true });
    return;
  }
  await enviarEtiquetaBolsaTermica(ordenAbierta.value.id, { indice });
}

async function guardarOrdenAbierta({ finalizar }) {
  if (!cajaAbierta.value) return null;
  if (!clienteSeleccionado.value?.id) {
    showMessage(t("ventas.pos.validation.cliente_select_required"), "warning");
    return null;
  }
  if (finalizar && carrito.value.length === 0) {
    showMessage(t("ventas.pos.validation.finalize_need_items"), "warning");
    return null;
  }
  serverErrors.value = {};
  if (carrito.value.length > 0) {
    if (timeoutPreview) clearTimeout(timeoutPreview);
    await syncCartPreviewTotals();
  }
  const ok = await v$.value.$validate();
  if (!ok) return null;
  if (carrito.value.length > 0 && !validateLineItems()) return null;

  const tOrden = round2(totalCarrito.value);
  if (finalizar && tOrden > 0) {
    const linesPositivas = paymentLines.value
      .map((pl) => ({ metodo_pago: pl.metodo_pago, monto: round2(Number(pl.monto) || 0) }))
      .filter((x) => x.monto > 0);
    if (linesPositivas.length === 0) {
      showMessage(t("ventas.pos.payments_finalize_required"), "warning");
      return null;
    }
    const sum = round2(linesPositivas.reduce((s, x) => s + x.monto, 0));
    if (Math.abs(sum - tOrden) > 0.02) {
      showMessage(
        t("ventas.pos.payments_sum_mismatch", { sum: sum.toFixed(2), total: tOrden.toFixed(2) }),
        "warning"
      );
      return null;
    }
  }

  if (!form.tipo_servicio_id) {
    showMessage(t("ventas.pos.order_tipo_servicio_required"), "warning");
    return null;
  }
  if (
    form.semaphore_hours_red == null ||
    form.semaphore_hours_red === "" ||
    form.semaphore_hours_yellow == null ||
    form.semaphore_hours_yellow === ""
  ) {
    showMessage(t("ventas.pos.semaphore_required"), "warning");
    return null;
  }

  creando.value = true;
  isLoading.value = true;

  const payload = {
    cliente_nombre: form.cliente_nombre,
    cliente_email: form.cliente_email || undefined,
    cliente_id: clienteSeleccionado.value?.id || undefined,
    detalles_ropa: form.detalles_ropa || undefined,
    instrucciones: form.instrucciones || undefined,
    mostrar_observaciones_en_ticket: form.mostrar_observaciones_en_ticket,
    is_priority: isPriorityFromTipoId(form.tipo_servicio_id),
    tipo_servicio_id: form.tipo_servicio_id,
    semaphore_hours_red: Math.max(0, Math.min(168, Math.round(Number(form.semaphore_hours_red)))),
    semaphore_hours_yellow: Math.max(0, Math.min(168, Math.round(Number(form.semaphore_hours_yellow)))),
    is_delivery: form.is_delivery,
    delivery_address: form.is_delivery ? form.delivery_address || undefined : undefined,
    fecha_entrega: datetimeLocalToServerPayload(form.fecha_entrega) || undefined,
  };

  if (finalizar) {
    payload.finalizar = true;
    if (tOrden > 0) {
      const lines = paymentLines.value
        .map((pl) => ({ metodo_pago: pl.metodo_pago, monto: round2(Number(pl.monto) || 0) }))
        .filter((x) => x.monto > 0);
      if (lines.length > 0) {
        payload.payments = lines;
      }
    }
  }
  if (ordenAbierta.value?.id) {
    payload.items = buildItemsPayload();
  } else if (carrito.value.length > 0) {
    payload.items = buildItemsPayload();
  }

  try {
    const isUpdate = !!ordenAbierta.value?.id;
    const response = isUpdate
      ? await OrdenLavanderiaRepository.updateOrden(ordenAbierta.value.id, payload)
      : await OrdenLavanderiaRepository.createOrden(payload);
    if (response.data) {
      const orden = response.data;
      ordenAbierta.value = orden;
      syncBolsasDraftFromOrden(orden);
      persistActiveOrderId(orden.id);
      persistRecentFolio(orden.folio_unico);
      if (ordenGenerada.value?.id === orden.id) {
        ordenGenerada.value = orden;
      }
      await maybeUpdateClienteDireccion();
      const impresion = response.impresion_venta;
      if (finalizar && impresion) {
        if (impresion.success) {
          showMessage(t("ventas.pos.sale_print_ok"), "success");
        } else {
          showMessage(t("ventas.pos.sale_print_warn", { msg: impresion.error || "" }), "warning");
        }
      } else {
        showMessage(
          isUpdate
            ? t("ventas.pos.changes_saved")
            : t("ventas.pos.order_opened", { folio: orden.folio_unico }),
          "success"
        );
      }
      return orden;
    }
  } catch (error) {
    if (axios.isAxiosError(error)) {
      serverErrors.value = error.response?.data?.errors || {};
      const msg =
        error.code === "ECONNABORTED"
          ? t("ventas.pos.sale_timeout")
          : error.response?.data?.message || error.message;
      showMessage(t("ventas.pos.sale_error", { msg }), "error");
    }
  } finally {
    creando.value = false;
    isLoading.value = false;
  }
  return null;
}

async function aplicarClienteMostrador() {
  try {
    const resMostrador = await ClienteRepository.getClienteMostrador();
    const data = resMostrador?.data ?? resMostrador;
    if (data) seleccionarCliente(data);
  } catch {
    /* opcional */
  }
}

async function nuevaOrdenVacia() {
  if (ordenAbierta.value?.id && cajaAbierta.value) {
    await guardarOrdenAbierta({ finalizar: false });
  }
  clearActiveOrderId();
  ordenAbierta.value = null;
  folioReapertura.value = "";
  resetForm();
  await aplicarClienteMostrador();
}

function cerrarOrdenAbierta() {
  return nuevaOrdenVacia();
}

function persistActiveOrderId(id) {
  try {
    const s = String(id);
    localStorage.setItem(ACTIVE_ORDER_STORAGE_KEY, s);
    localStorage.setItem(OPEN_ORDER_STORAGE_KEY, s);
  } catch {
    /* ignore */
  }
}

function persistOrdenAbiertaId(id) {
  persistActiveOrderId(id);
}

function readRecentFolios() {
  try {
    const raw = localStorage.getItem(RECENT_OPEN_FOLIOS_STORAGE_KEY);
    const parsed = raw ? JSON.parse(raw) : [];
    return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string" && x.trim()) : [];
  } catch {
    return [];
  }
}

function persistRecentFolio(folio) {
  const value = String(folio || "").trim();
  if (!value) return;
  const next = [value, ...foliosRecientes.value.filter((f) => f !== value)].slice(0, 12);
  foliosRecientes.value = next;
  try {
    localStorage.setItem(RECENT_OPEN_FOLIOS_STORAGE_KEY, JSON.stringify(next));
  } catch {
    /* ignore */
  }
}

function readActiveOrderId() {
  try {
    let raw = localStorage.getItem(ACTIVE_ORDER_STORAGE_KEY);
    if (!raw) raw = localStorage.getItem(OPEN_ORDER_STORAGE_KEY);
    const id = raw ? Number(raw) : NaN;
    return Number.isFinite(id) && id > 0 ? id : null;
  } catch {
    return null;
  }
}

function readOrdenAbiertaId() {
  return readActiveOrderId();
}

function clearActiveOrderId() {
  try {
    localStorage.removeItem(ACTIVE_ORDER_STORAGE_KEY);
    localStorage.removeItem(OPEN_ORDER_STORAGE_KEY);
  } catch {
    /* ignore */
  }
}

function clearOrdenAbiertaId() {
  clearActiveOrderId();
}

function isOrdenNotFoundError(error) {
  if (!axios.isAxiosError(error)) return false;
  return error.response?.status === 404;
}

function limpiarOrdenAlmacenadaSiCoincide(id) {
  if (readActiveOrderId() === Number(id)) {
    clearActiveOrderId();
  }
  if (ordenAbierta.value?.id === Number(id)) {
    ordenAbierta.value = null;
  }
}

/**
 * @param {number|string} id
 * @param {{ silent?: boolean, showSuccess?: boolean }} [opts]
 */
async function cargarOrdenPorId(id, opts = {}) {
  const orderId = Number(id);
  if (!Number.isFinite(orderId) || orderId <= 0) return false;

  const silent = !!opts.silent;
  const showSuccess = opts.showSuccess !== false;

  buscandoOrdenByFolio.value = true;
  try {
    const full = await OrdenLavanderiaRepository.getById(orderId);
    const data = full?.data;
    if (!data || !puedeEditarOrdenEnPos(data)) {
      limpiarOrdenAlmacenadaSiCoincide(orderId);
      if (!silent) {
        showMessage(t("ventas.pos.reopen_order_not_found"), "warning");
      }
      return false;
    }
    ordenAbierta.value = data;
    persistActiveOrderId(data.id);
    persistRecentFolio(data.folio_unico);
    await hydratePosFromOrden(data);
    if (showSuccess) {
      showMessage(t("ventas.pos.reopen_order_ok", { folio: data.folio_unico }), "success");
    }
    return true;
  } catch (error) {
    if (isOrdenNotFoundError(error)) {
      limpiarOrdenAlmacenadaSiCoincide(orderId);
      if (!silent) {
        showMessage(t("ventas.pos.reopen_order_stale_storage"), "warning");
      }
      return false;
    }
    if (!silent) {
      showMessage(t("ventas.pos.reopen_order_error"), "error");
    }
    return false;
  } finally {
    buscandoOrdenByFolio.value = false;
  }
}

function setClienteDesdeOrden(orden) {
  if (orden?.cliente_id && orden?.cliente) {
    clienteSeleccionado.value = {
      id: orden.cliente.id,
      nombre: orden.cliente.nombre,
      email: orden.cliente.email || "",
      telefono: orden.cliente.telefono || "",
      direccion: orden.cliente.direccion || "",
      es_mostrador: !!orden.cliente.es_mostrador,
      cliente_categoria: orden.cliente.cliente_categoria || orden.cliente.clienteCategoria || null,
      cliente_categoria_id: orden.cliente.cliente_categoria_id ?? null,
    };
    form.cliente_nombre = orden.cliente.nombre;
    form.cliente_email = orden.cliente.email || "";
    busquedaCliente.value = orden.cliente.nombre;
    return;
  }
  if (orden?.cliente_id && orden?.cliente_nombre) {
    clienteSeleccionado.value = {
      id: orden.cliente_id,
      nombre: orden.cliente_nombre,
      email: orden.cliente_email || "",
      direccion: "",
    };
    form.cliente_nombre = orden.cliente_nombre;
    form.cliente_email = orden.cliente_email || "";
    busquedaCliente.value = orden.cliente_nombre;
    return;
  }
  clienteSeleccionado.value = null;
  form.cliente_nombre = orden?.cliente_nombre || "";
  form.cliente_email = orden?.cliente_email || "";
  busquedaCliente.value = "";
}

function isCustomCatalogPrice(detalle) {
  if (!detalle?.laundry_sale_product_id) return false;
  const catalogPrice = catalogDefaultUnitPrice(detalle.laundry_sale_product_id);
  if (catalogPrice == null) return false;
  return Math.abs(Number(detalle.precio_unitario ?? 0) - catalogPrice) > 0.009;
}

async function hydratePosFromOrden(orden) {
  setClienteDesdeOrden(orden);
  form.detalles_ropa = orden?.detalles_ropa || "";
  form.instrucciones = orden?.instrucciones || "";
  form.mostrar_observaciones_en_ticket = orden?.mostrar_observaciones_en_ticket !== false;
  form.is_delivery = !!(orden?.shipping_order?.is_delivery ?? orden?.is_delivery);
  form.delivery_address = orden?.shipping_order?.delivery_address || "";
  form.fecha_entrega = orden?.fecha_entrega
    ? parseIsoToDatetimeLocal(orden.fecha_entrega)
    : defaultFechaEntregaLocal({ days: defaultDeliveryDays.value, hour: defaultDeliveryHour.value });
  form.tipo_servicio_id =
    orden?.tipo_servicio_id ?? orden?.sla_tipo_servicio?.id ?? orden?.slaTipoServicio?.id ?? defaultTipoServicioId();
  form.semaphore_hours_red =
    orden?.semaphore_hours_red != null ? Number(orden.semaphore_hours_red) : defaultSemaphoreRed.value;
  form.semaphore_hours_yellow =
    orden?.semaphore_hours_yellow != null ? Number(orden.semaphore_hours_yellow) : defaultSemaphoreYellow.value;
  expandedCartUid.value = null;
  syncBolsasDraftFromOrden(orden);
  const detalles = Array.isArray(orden?.detalles) ? orden.detalles : [];
  let lastCatalogUid = null;
  carrito.value = [];
  for (const d of detalles.filter(
    (row) => row?.is_informational || row?.etapa_id || (row?.laundry_sale_product_id && !row?.garment_type_id)
  )) {
    const lineUid = uid();
    if (d.is_informational) {
      carrito.value.push({
        uid: lineUid,
        is_informational: true,
        parent_uid: lastCatalogUid,
        laundry_sale_product_id: d.laundry_sale_product_id ?? null,
        garment_type_id: d.garment_type_id ?? null,
        cantidad: Number(d.cantidad || 1),
        observations: d.observations || "",
      });
      continue;
    }
    lastCatalogUid = lineUid;
    carrito.value.push({
      uid: lineUid,
      laundry_sale_product_id: d.laundry_sale_product_id ?? null,
      etapa_id: d.etapa_id ?? null,
      garment_type_id: d.garment_type_id ?? null,
      load_type_id: d.load_type_id ?? null,
      billing_mode: d.billing_mode || "by_garment_type",
      cantidad: Number(d.cantidad || 1),
      weight_kg: d.weight_kg != null ? Number(d.weight_kg) : null,
      use_custom_price: isCustomCatalogPrice(d),
      custom_unit_price: Number(d.precio_unitario ?? 0),
      observations: d.observations || "",
    });
  }
  await syncCartPreviewTotals();
  hydratePaymentsFromOrden(orden);
}

async function reabrirOrdenPorFolio() {
  const folio = String(folioReapertura.value || "").trim();
  if (!folio) return;
  buscandoOrdenByFolio.value = true;
  try {
    const body = await OrdenLavanderiaRepository.getAll({
      folio_unico: folio,
      estatus: "pendiente",
      current_step: "recepcion",
      per_page: 5,
      page: 1,
    });
    const page = body?.data;
    const rows = Array.isArray(page?.data) ? page.data : [];
    const orden = rows.find((r) => String(r.folio_unico || "").toLowerCase() === folio.toLowerCase()) || rows[0];
    if (!orden?.id) {
      showMessage(t("ventas.pos.reopen_order_not_found"), "warning");
      return;
    }
    await cargarOrdenPorId(orden.id);
  } catch {
    showMessage(t("ventas.pos.reopen_order_error"), "error");
  } finally {
    buscandoOrdenByFolio.value = false;
  }
}

async function reopenOrderFromRouteIfNeeded() {
  const editId = Number(route.query?.edit_id || 0);
  if (editId > 0) {
    await cargarOrdenPorId(editId);
    return;
  }
  const fromQuery = String(route.query?.edit_folio || "").trim();
  if (!fromQuery) return;
  folioReapertura.value = fromQuery;
  await reabrirOrdenPorFolio();
}

/**
 * Ticket 80 mm con dos páginas idénticas (mismo QR). La API de creación ya devuelve detalles.producto y observations.
 */
function asegurarImpresoraRecepcion() {
  if (receptionPrinterConfigured.value) return true;
  showMessage(t("ventas.pos.print_thermal_no_printer"), "warning");
  return false;
}

async function enviarTicketTermicoRecepcion(ordenId) {
  if (!ordenId || !asegurarImpresoraRecepcion()) return false;
  imprimiendoTicketTermico.value = ordenId;
  try {
    const body = await TicketRepository.reimprimirTicket(ordenId, null, { posReception: true });
    if (body?.success || body?.impresion?.success) {
      showMessage(t("ventas.pos.print_thermal_ok"), "success");
      return true;
    }
    const msg = body?.impresion?.error || body?.message || "";
    showMessage(t("ventas.pos.sale_print_warn", { msg }), "warning");
    return false;
  } catch (error) {
    const msg =
      error.response?.data?.impresion?.error ||
      error.response?.data?.message ||
      t("ventas.pos.pdf_error");
    showMessage(t("ventas.pos.sale_print_warn", { msg }), "warning");
    return false;
  } finally {
    imprimiendoTicketTermico.value = null;
  }
}

async function imprimirTicketTermicoOrdenAbierta() {
  if (!ordenAbierta.value?.id) return;
  await enviarTicketTermicoRecepcion(ordenAbierta.value.id);
}

async function imprimirTicketTermicoOrdenGenerada() {
  if (!ordenGenerada.value?.id) return;
  await enviarTicketTermicoRecepcion(ordenGenerada.value.id);
}

async function abrirTicketDosQrConOrden(orden) {
  if (!orden?.folio_unico) return;
  descargandoPdf.value = true;
  try {
    const win = await openTicketPdfTwoQrCopies(orden);
    if (win === null) {
      showMessage(t("ventas.pos.ticket_popup_blocked"), "warning");
    }
  } catch {
    showMessage(t("ventas.pos.pdf_error"), "error");
  } finally {
    descargandoPdf.value = false;
  }
}

async function imprimirTicketJsPdf() {
  if (!ordenGenerada.value?.id) return;
  descargandoPdf.value = true;
  try {
    const full = await OrdenLavanderiaRepository.getById(ordenGenerada.value.id);
    const data = full?.data ?? ordenGenerada.value;
    const win = await openTicketPdfTwoQrCopies(data);
    if (win === null) {
      showMessage(t("ventas.pos.ticket_popup_blocked"), "warning");
    }
  } catch {
    showMessage(t("ventas.pos.pdf_error"), "error");
  } finally {
    descargandoPdf.value = false;
  }
}

async function imprimirTicketServidor() {
  if (!ordenGenerada.value?.id) return;
  descargandoPdfServer.value = true;
  try {
    const res = await ApiService.get(`lavanderia/ordenes/${ordenGenerada.value.id}/ticket-pdf`, { responseType: "blob" });
    const url = URL.createObjectURL(new Blob([res.data], { type: "application/pdf" }));
    window.open(url, "_blank");
  } catch {
    showMessage(t("ventas.pos.pdf_error"), "error");
  } finally {
    descargandoPdfServer.value = false;
  }
}

function cerrarModalOrden() {
  mostrarModalOrden.value = false;
  ordenGenerada.value = null;
}

onMounted(async () => {
  posInicializando.value = true;
  autoImprimirTicket.value = readAutoImprimirTicket();
  foliosRecientes.value = readRecentFolios();
  try {
    await cargarCaja();
    await cargarCatalogosPos();
    await cargarCategoriasClientesPos();
    if (route.query?.edit_id || route.query?.edit_folio) {
      await reopenOrderFromRouteIfNeeded();
    } else if (!ordenAbierta.value) {
      await aplicarClienteMostrador();
    }
  } finally {
    posInicializando.value = false;
  }
});
</script>

<style scoped>
.modal.pos-modal-overlay {
  display: block;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1055;
}

.modal.pos-modal-overlay-top {
  z-index: 1070;
}

.card {
  transition: all 0.3s ease;
}

.pos-open-orders-panel {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 0.75rem;
  background: #fafafa;
}

.pos-catalog-modal-dialog {
  max-width: min(96vw, 1280px);
  width: 96vw;
  margin: 0.75rem auto;
}

.pos-catalog-modal-dialog .modal-content {
  min-height: min(88vh, 900px);
}

.pos-catalog-modal-dialog .modal-body {
  min-height: 52vh;
  max-height: min(70vh, 680px);
}

.pos-catalog-modal-dialog .pos-catalog-grid {
  grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));
  gap: 0.85rem;
}

.pos-catalog-modal-dialog .pos-catalog-grid--services {
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}

.pos-catalog-modal-dialog .pos-catalog-tile {
  min-height: 5.25rem;
  padding: 0.9rem 1rem;
}

.pos-catalog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 0.75rem;
  align-items: stretch;
}

.pos-catalog-grid--services {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

.pos-catalog-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  height: 100%;
  min-height: 4.5rem;
  padding: 0.75rem 0.85rem;
  border-radius: 10px;
  white-space: normal;
  text-align: left;
  overflow: hidden;
}

.pos-catalog-tile-name {
  font-weight: 600;
  line-height: 1.3;
  word-break: break-word;
  overflow-wrap: anywhere;
  width: 100%;
  flex: 1 1 auto;
}

.pos-catalog-tile--service .pos-catalog-tile-name {
  margin-bottom: 0.5rem;
}

.pos-catalog-tile-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  width: 100%;
  margin-top: auto;
  flex-shrink: 0;
}

.pos-catalog-tile-price {
  font-size: 0.875rem;
  color: #6b7280;
  font-weight: 500;
}

.pos-catalog-tile-meta .badge {
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
}

.pos-flow-badge {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #e85a10;
  background: #fff0e8;
  border-radius: 6px;
  padding: 0.2rem 0.55rem;
}

.pos-catalog-panel {
  border-left: 4px solid #ff7029;
}

.pos-add-service-btn {
  line-height: 1.35;
  gap: 0.35rem;
  text-align: center;
}

.pos-catalog-steps {
  font-size: 0.8rem;
}

.pos-catalog-step-pill {
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: #f3f4f6;
  color: #6b7280;
  font-weight: 500;
}

.pos-catalog-step-pill.active {
  background: #ff7029;
  color: #fff;
}

.pos-catalog-step-pill.done {
  background: #e6f7fc;
  color: #0082a3;
}

.pos-cart-panel {
  max-height: min(52vh, 520px);
  overflow-y: auto;
}

.pos-cart-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.pos-cart-item {
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  background: #fff;
  overflow: hidden;
}

.pos-cart-item-head {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.65rem 0.75rem;
}

.pos-cart-item-body {
  border-top: 1px solid #f0f0f0;
  background: #fafafa;
  padding: 0.65rem 0.75rem;
}

.pos-cart-empty {
  border: 1px dashed #d1d5db;
  border-radius: 10px;
  background: #fafafa;
}
</style>
