# Escala de z-index + BaseSheet para overlays de la PWA — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Que ningún overlay de la PWA quede tapado por la barra de navegación ni por el FAB, corrigiendo de raíz el stacking con una escala de z-index documentada, un componente `BaseSheet` compartido, el arreglo de 2 barras CTA, y ocultando el FAB en vistas con acción propia.

**Architecture:** Se añade una escala de z-index por **tokens CSS** (`:root` en `pwa.css`) con un tier de overlays/modales/toasts **por encima** de la banda del `BottomNav` (100–500) — sin tocar esa banda. Un nuevo `components/ui/BaseSheet.vue` (Teleport a `body`, backdrop `--z-overlay`, panel `--z-modal`, padding inferior que reserva el alto del nav) reemplaza el markup hand-rolled de los 4 modales. Las 2 barras CTA fijas y los toasts pasan a los tokens; el FAB del `BottomNav` se oculta por `route.meta.hidePrimaryFab`.

**Tech Stack:** Vue 3 (`<script setup>`), Tailwind v4 CSS-first (`pwa.css` con `@import 'tailwindcss'`), Vite. **No hay tests JS de la PWA** → verificación = `npm run build` sin errores + revisión de código/preview; el check final en dispositivo (login) lo hace el usuario.

**Spec:** `docs/superpowers/specs/2026-08-01-pwa-overlay-zindex-design.md`

**Gotchas (memoria del proyecto):**
- **Dark scoped CSS:** `:global(html.dark) .sel` miscompila (pierde el descendiente) → usar `:global(html.dark .sel)`. (credify-pwa-dark-scoped-css)
- **Commits desde WSL** (`/var/www/html/credify`), no incluir `.claude/`. (credify-wsl-workflow)
- **Deploy full** (assets): al final cambia el bundle de la PWA → build + ship de `public/build` + `public/pwa-sw.js` a prod. (credify-prod-vm)
- Los `for`-loops y `$()` dentro de `ssh '...'`/`wsl bash -lc "..."` se mutilan → usar scripts en `/tmp`.

---

## File Structure

```
resources/js/pwa/
  assets/pwa.css                          MODIFY  tokens :root (--z-*, --pwa-nav-space)
  components/ui/BaseSheet.vue             CREATE  overlay compartido (teleport + z + nav-safe)
  components/credits/PaymentHistory.vue   MODIFY  modal Anular -> BaseSheet (el reportado)
  components/collections/NoPaymentModal.vue MODIFY -> BaseSheet (align=end)
  components/dashboard/QuickActionRow.vue MODIFY  -> BaseSheet (align=end)
  components/payments/PaymentReceipt.vue  MODIFY  -> BaseSheet (align=center)
  views/CompanySettingsView.vue           MODIFY  barra Guardar -> --z-page-cta + sobre el nav
  views/CreditDetailView.vue              MODIFY  barra CTA -> --z-page-cta
  components/ui/ToastContainer.vue         MODIFY  z -> --z-toast
  views/ExpenseCreateView.vue             MODIFY  pill toast z -> --z-toast
  views/PartnerTransactionView.vue         MODIFY  pill toast z -> --z-toast
  views/ApprovalsView.vue                  MODIFY  pill toast z -> --z-toast
  views/CreditOrderView.vue                MODIFY  pill z -> --z-toast
  components/layout/BottomNav.vue          MODIFY  FAB v-if="!route.meta.hidePrimaryFab"
  router/index.js                          MODIFY  meta.hidePrimaryFab en 2 rutas
CHANGELOG.md                               MODIFY
```

---

### Task 1: Tokens de z-index + espacio del nav (`pwa.css`)

**Files:**
- Modify: `resources/js/pwa/assets/pwa.css`

- [ ] **Step 1: Añadir el bloque de tokens**

En `resources/js/pwa/assets/pwa.css`, tras el bloque `@layer base { ... }` inicial (después de la línea que cierra ese layer, cerca del top del archivo), añade:

```css
/* ────────────────────────────────────────────
   Escala de z-index de la PWA (aditiva).
   La banda del BottomNav (barra 100, backdrop 200,
   sheet 400, FAB 500) NO se toca; estos tiers van por
   encima de 500. Ver spec 2026-08-01-pwa-overlay-zindex.
──────────────────────────────────────────── */
:root {
    /* Alto real que ocupa el BottomNav (coincide con el offset del sheet del FAB) */
    --pwa-nav-space: calc(env(safe-area-inset-bottom) + 82px);

    --z-page-cta: 600;   /* barras de acción fijas de página, por encima del FAB (500) */
    --z-overlay:  1000;  /* backdrop de modal/hoja */
    --z-modal:    1010;  /* panel de modal/hoja */
    --z-toast:    1100;  /* toasts, por encima de todo */
}
```

- [ ] **Step 2: Verificar build**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -3"
```
Expected: build sin errores (línea `built in`). (Los tokens son CSS plano; no rompen nada.)

- [ ] **Step 3: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/assets/pwa.css && git commit -m 'feat(pwa): tokens de z-index + --pwa-nav-space'"
```

---

### Task 2: Componente `BaseSheet.vue`

Overlay compartido: teleport a `body`, backdrop `--z-overlay`, panel `--z-modal`, cierre por click-fuera/Escape, bloqueo de scroll, y en `align=end` reserva `--pwa-nav-space` para que los botones nunca caigan tras el nav.

**Files:**
- Create: `resources/js/pwa/components/ui/BaseSheet.vue`

- [ ] **Step 1: Crear el componente**

`resources/js/pwa/components/ui/BaseSheet.vue`:

```vue
<template>
    <Teleport to="body">
        <Transition name="bs-fade">
            <div
                v-if="open"
                class="bs-backdrop"
                aria-hidden="true"
                @click="onBackdrop"
            ></div>
        </Transition>
        <Transition :name="align === 'center' ? 'bs-pop' : 'bs-slide'">
            <div
                v-if="open"
                class="bs-layer"
                :class="`bs-layer--${align}`"
                role="dialog"
                aria-modal="true"
            >
                <div class="bs-panel" @click.stop>
                    <slot />
                    <div v-if="$slots.actions" class="bs-actions">
                        <slot name="actions" />
                    </div>
                </div>
            </div>
        </Transition>
    </Teleport>
</template>

<script setup>
import { watch, onUnmounted } from 'vue'

const props = defineProps({
    open: { type: Boolean, default: false },
    align: { type: String, default: 'end' }, // 'end' (bottom-sheet) | 'center'
    dismissible: { type: Boolean, default: true },
})
const emit = defineEmits(['update:open'])

function close() { emit('update:open', false) }
function onBackdrop() { if (props.dismissible) close() }
function onKey(e) { if (e.key === 'Escape' && props.dismissible) close() }

watch(() => props.open, (v) => {
    if (typeof document === 'undefined') return
    document.documentElement.style.overflow = v ? 'hidden' : ''
    if (v) document.addEventListener('keydown', onKey)
    else document.removeEventListener('keydown', onKey)
}, { immediate: true })

onUnmounted(() => {
    if (typeof document !== 'undefined') document.documentElement.style.overflow = ''
    document.removeEventListener('keydown', onKey)
})
</script>

<style scoped>
.bs-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-overlay);
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
}
.bs-layer {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    justify-content: center;
    padding: 1rem;
    pointer-events: none; /* deja pasar el click al backdrop; solo el panel captura */
}
.bs-layer--end {
    align-items: flex-end;
    padding-bottom: calc(var(--pwa-nav-space) + 1rem);
}
.bs-layer--center {
    align-items: center;
}
.bs-panel {
    width: 100%;
    max-width: 24rem;
    pointer-events: auto;
}
.bs-actions {
    margin-top: 1rem;
    display: flex;
    gap: 0.5rem;
}
/* transiciones */
.bs-fade-enter-active, .bs-fade-leave-active { transition: opacity 0.2s ease; }
.bs-fade-enter-from, .bs-fade-leave-to { opacity: 0; }
.bs-slide-enter-active, .bs-slide-leave-active { transition: transform 0.25s ease, opacity 0.25s ease; }
.bs-slide-enter-from, .bs-slide-leave-to { transform: translateY(24px); opacity: 0; }
.bs-pop-enter-active, .bs-pop-leave-active { transition: transform 0.2s ease, opacity 0.2s ease; }
.bs-pop-enter-from, .bs-pop-leave-to { transform: scale(0.96); opacity: 0; }
</style>
```

Nota: el panel `align=center` no necesita `max-width` distinto; los modales que migren pueden envolver su propio contenido con su ancho (p. ej. `max-w-sm`) dentro del slot si lo requieren.

- [ ] **Step 2: Verificar build**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -3"
```
Expected: build sin errores.

- [ ] **Step 3: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/ui/BaseSheet.vue && git commit -m 'feat(pwa): componente BaseSheet (overlay teleport + z-index + nav-safe)'"
```

---

### Task 3: Migrar el modal "Anular pago" (`PaymentHistory.vue`) — el bug reportado

El modal está en `PaymentHistory.vue` (~líneas 93-138): un `<div v-if="voidingPayment" class="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-4" style="background: rgba(0,0,0,0.6);" @click.self="closeVoidModal">` con un `<div class="w-full max-w-sm rounded-2xl p-5 pwa-card">…contenido…</div>`. Se controla con `voidingPayment` (objeto|null) y `closeVoidModal()`.

**Files:**
- Modify: `resources/js/pwa/components/credits/PaymentHistory.vue`

- [ ] **Step 1: Importar BaseSheet**

En el `<script setup>` de `PaymentHistory.vue`, junto a los demás imports, añade:
```js
import BaseSheet from '../ui/BaseSheet.vue'
```

- [ ] **Step 2: Reemplazar el wrapper del modal por BaseSheet**

Reemplaza el bloque `<!-- Void modal --> <div v-if="voidingPayment" class="fixed inset-0 z-50 ..." ...> ... </div>` por:

```vue
    <!-- Void modal -->
    <BaseSheet :open="!!voidingPayment" align="end" @update:open="v => { if (!v) closeVoidModal() }">
        <div class="w-full rounded-2xl p-5 pwa-card">
            <!-- (mismo contenido interno que ya existía: h3 "Anular pago", el <p>, el <label> Motivo,
                 el <textarea v-model="voidReason">, el <div v-if="voidError">, y el <div class="mt-4 flex gap-2">
                 con los botones Cancelar/Anular pago — pégalo TAL CUAL dentro de este div) -->
        </div>
    </BaseSheet>
```

Es decir: quita el `<div class="fixed inset-0 z-50 …" style="background…" @click.self="closeVoidModal">` externo (BaseSheet aporta backdrop + z + cierre), quita el `max-w-sm` del div interno (BaseSheet ya limita a `max-w-sm`), y conserva **todo el contenido interior sin cambios**. El botón "Anular pago" sigue llamando `@click="confirmVoid"`.

- [ ] **Step 3: Verificar build + que no queda z-50 en el modal**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2"
wsl bash -lc "cd /var/www/html/credify && grep -nE 'fixed inset-0 z-50|BaseSheet' resources/js/pwa/components/credits/PaymentHistory.vue"
```
Expected: build OK; el grep muestra el uso de `<BaseSheet` y **ya no** `fixed inset-0 z-50`.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/credits/PaymentHistory.vue && git commit -m 'fix(pwa): modal Anular pago usa BaseSheet (ya no queda tras el nav)'"
```

---

### Task 4: Migrar `NoPaymentModal.vue` → BaseSheet (align=end)

Este ya usa `v-model` (`v-if="modelValue"`, `close()` emite `update:modelValue false`) y está teleportado con su propio `<Teleport><Transition>` + backdrop + `fixed inset-0 z-50 items-end` (líneas ~2-15). Se reemplaza ese andamiaje por BaseSheet.

**Files:**
- Modify: `resources/js/pwa/components/collections/NoPaymentModal.vue`

- [ ] **Step 1: Importar BaseSheet**

En `<script setup>` de `NoPaymentModal.vue`:
```js
import BaseSheet from '../ui/BaseSheet.vue'
```

- [ ] **Step 2: Reemplazar Teleport/Transition/backdrop por BaseSheet**

Reemplaza desde `<Teleport to="body"> <Transition name="modal"> <div v-if="modelValue" class="fixed inset-0 z-50 flex items-end justify-center"> <div class="absolute inset-0 bg-black/50" @click="close"></div>` … hasta el cierre de ese Teleport/Transition, por:

```vue
    <BaseSheet :open="modelValue" align="end" @update:open="v => { if (!v) close() }">
        <div class="relative bg-white rounded-3xl w-full max-h-[85vh] overflow-y-auto animate-slide-up">
            <!-- (mismo contenido interno del modal: header "No Paga Hoy", cuerpo, y el botón "Registrar Visita".
                 Pega el contenido que estaba dentro del "Modal content" original, SIN el safe-area-bottom
                 —BaseSheet ya reserva el nav— y sin el rounded-t-3xl -> usa rounded-3xl) -->
        </div>
    </BaseSheet>
```

Notas: elimina el `<Teleport>`, `<Transition name="modal">`, el `<div class="fixed inset-0 z-50 …">` y el backdrop `<div class="absolute inset-0 bg-black/50">` (BaseSheet los aporta). Quita la clase `safe-area-bottom` del contenido (BaseSheet reserva el nav). Si el `<Transition name="modal">`/`animate-slide-up` daba la animación, BaseSheet ya anima con `bs-slide`.

- [ ] **Step 3: Verificar build**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2 && grep -nE 'fixed inset-0 z-50|BaseSheet' resources/js/pwa/components/collections/NoPaymentModal.vue"
```
Expected: build OK; `<BaseSheet` presente, sin `fixed inset-0 z-50`.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/collections/NoPaymentModal.vue && git commit -m 'fix(pwa): NoPaymentModal usa BaseSheet'"
```

---

### Task 5: Migrar `QuickActionRow.vue` → BaseSheet (align=end)

La hoja "Más acciones" es un `<div v-if="sheetOpen" class="fixed inset-0 z-50 flex items-end" @click.self="sheetOpen = false">` (línea ~26) con las tiles de acción dentro. Se controla con `sheetOpen` (ref local).

**Files:**
- Modify: `resources/js/pwa/components/dashboard/QuickActionRow.vue`

- [ ] **Step 1: Importar BaseSheet**

En `<script setup>`:
```js
import BaseSheet from '../ui/BaseSheet.vue'
```

- [ ] **Step 2: Reemplazar el wrapper de la hoja por BaseSheet**

Reemplaza el `<div v-if="sheetOpen" class="fixed inset-0 z-50 flex items-end" @click.self="sheetOpen = false"> … </div>` por:

```vue
    <BaseSheet :open="sheetOpen" align="end" @update:open="v => sheetOpen = v">
        <!-- (mismo contenido interno: el contenedor de tiles de acción que ya estaba dentro) -->
    </BaseSheet>
```

Conserva el contenido interno (las tiles/lista). Si había un backdrop propio dentro, quítalo (BaseSheet lo aporta). Los clicks en las tiles siguen cerrando la hoja como antes (mantén sus `@click`).

- [ ] **Step 3: Verificar build**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2 && grep -nE 'fixed inset-0 z-50|BaseSheet' resources/js/pwa/components/dashboard/QuickActionRow.vue"
```
Expected: build OK; `<BaseSheet` presente, sin `fixed inset-0 z-50`.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/dashboard/QuickActionRow.vue && git commit -m 'fix(pwa): QuickActionRow usa BaseSheet'"
```

---

### Task 6: Migrar `PaymentReceipt.vue` → BaseSheet (align=center)

Diálogo centrado (`v-if="modelValue"`, `fixed inset-0 z-50 flex items-center justify-center p-4`, backdrop + `close()` que emite `update:modelValue`). Tiene una fila de acciones `sticky bottom-0` (Cerrar/Compartir) dentro de su caja scrollable.

**Files:**
- Modify: `resources/js/pwa/components/payments/PaymentReceipt.vue`

- [ ] **Step 1: Importar BaseSheet**

En `<script setup>`:
```js
import BaseSheet from '../ui/BaseSheet.vue'
```

- [ ] **Step 2: Reemplazar Teleport/Transition/backdrop por BaseSheet (align=center)**

Reemplaza el `<Teleport><Transition><div v-if="modelValue" class="fixed inset-0 z-50 flex items-center justify-center p-4"><div class="absolute inset-0 bg-black/50" @click="close">…` por:

```vue
    <BaseSheet :open="modelValue" align="center" @update:open="v => { if (!v) close() }">
        <div class="relative bg-white rounded-2xl w-full max-h-[90vh] overflow-y-auto shadow-xl">
            <!-- (mismo contenido interno del recibo: loading, cuerpo, y la fila sticky bottom-0 Cerrar/Compartir) -->
        </div>
    </BaseSheet>
```

Elimina `<Teleport>`, `<Transition>`, el `fixed inset-0 z-50` y el backdrop. Conserva el contenido y la fila de acciones interna.

- [ ] **Step 3: Verificar build**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2 && grep -nE 'fixed inset-0 z-50|BaseSheet' resources/js/pwa/components/payments/PaymentReceipt.vue"
```
Expected: build OK; `<BaseSheet` presente, sin `fixed inset-0 z-50`.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/payments/PaymentReceipt.vue && git commit -m 'fix(pwa): PaymentReceipt usa BaseSheet (centrado)'"
```

---

### Task 7: Barras CTA fijas → `--z-page-cta` + sobre el nav

Dos barras de acción fijas de página sin z-index quedan bajo/junto al nav.

**Files:**
- Modify: `resources/js/pwa/views/CompanySettingsView.vue` (línea ~299)
- Modify: `resources/js/pwa/views/CreditDetailView.vue` (línea ~118)

- [ ] **Step 1: `CompanySettingsView` — barra "Guardar Cambios"**

Reemplaza la etiqueta de apertura del `<div>` de la barra (actual: `<div class="fixed bottom-0 left-0 right-0 px-4 py-4 safe-area-bottom pwa-sticky-header" style="border-top-width: 1px;">`) por:

```html
        <div class="fixed left-0 right-0 px-4 py-4 pwa-sticky-header" style="bottom: var(--pwa-nav-space); z-index: var(--z-page-cta); border-top-width: 1px;">
```

(Cambios: quita `bottom-0` y `safe-area-bottom`; posiciona con `bottom: var(--pwa-nav-space)` para quedar **sobre** el nav; añade `z-index: var(--z-page-cta)`. El contenido del botón no cambia.)

- [ ] **Step 2: `CreditDetailView` — barra "No paga / Registrar Pago"**

Reemplaza `<div v-if="credit" class="fixed bottom-20 left-0 right-0 px-4 safe-area-bottom">` por:

```html
        <div v-if="credit" class="fixed left-0 right-0 px-4" style="bottom: var(--pwa-nav-space); z-index: var(--z-page-cta);">
```

(Cambios: quita `bottom-20` y `safe-area-bottom`; posiciona con `bottom: var(--pwa-nav-space)`; añade `z-index: var(--z-page-cta)`.)

- [ ] **Step 3: Verificar build**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2"
```
Expected: build OK.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/views/CompanySettingsView.vue resources/js/pwa/views/CreditDetailView.vue && git commit -m 'fix(pwa): barras CTA fijas por encima del nav (z-page-cta)'"
```

---

### Task 8: Toasts → `--z-toast`

Subir los toasts por encima del nav/FAB.

**Files:**
- Modify: `resources/js/pwa/components/ui/ToastContainer.vue` (línea 2)
- Modify: `resources/js/pwa/views/ExpenseCreateView.vue` (línea ~118)
- Modify: `resources/js/pwa/views/PartnerTransactionView.vue` (línea ~220)
- Modify: `resources/js/pwa/views/ApprovalsView.vue` (línea ~136)
- Modify: `resources/js/pwa/views/CreditOrderView.vue` (línea ~98)

- [ ] **Step 1: ToastContainer**

En `ToastContainer.vue` línea 2, en el `<div class="fixed bottom-20 left-4 right-4 z-50 flex flex-col gap-2 pointer-events-none">`: quita `z-50` y añade `style="z-index: var(--z-toast);"`. Queda:
```html
    <div class="fixed bottom-20 left-4 right-4 flex flex-col gap-2 pointer-events-none" style="z-index: var(--z-toast);">
```

- [ ] **Step 2: Los 4 pills de toast**

En cada uno, quita la clase `z-50`/`z-40` del contenedor del pill y añade `style="z-index: var(--z-toast);"` (si ya tiene `style`, agrega la propiedad):
- `ExpenseCreateView.vue` (~118): `class="fixed bottom-20 left-1/2 -translate-x-1/2 z-50 …"` → quita `z-50`, añade el style.
- `PartnerTransactionView.vue` (~220): `class="fixed bottom-20 left-1/2 -translate-x-1/2 z-50 …"` → igual.
- `ApprovalsView.vue` (~136): `class="fixed bottom-24 left-1/2 -translate-x-1/2 z-50 …"` → igual.
- `CreditOrderView.vue` (~98): `class="fixed bottom-24 left-0 right-0 flex justify-center px-4 z-40"` → quita `z-40`, añade `style="z-index: var(--z-toast);"`.

- [ ] **Step 3: Verificar build + que no quedan z-50/z-40 en esos 5 archivos**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2"
wsl bash -lc "cd /var/www/html/credify && grep -nE 'z-50|z-40' resources/js/pwa/components/ui/ToastContainer.vue resources/js/pwa/views/ExpenseCreateView.vue resources/js/pwa/views/PartnerTransactionView.vue resources/js/pwa/views/ApprovalsView.vue resources/js/pwa/views/CreditOrderView.vue || echo 'OK: sin z-50/z-40 residual'"
```
Expected: build OK; sin `z-50`/`z-40` residual en esos toasts.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/ui/ToastContainer.vue resources/js/pwa/views/ExpenseCreateView.vue resources/js/pwa/views/PartnerTransactionView.vue resources/js/pwa/views/ApprovalsView.vue resources/js/pwa/views/CreditOrderView.vue && git commit -m 'fix(pwa): toasts usan --z-toast (por encima del nav/FAB)'"
```

---

### Task 9: Pulido del FAB — ocultarlo en vistas con barra de acción propia

**Files:**
- Modify: `resources/js/pwa/components/layout/BottomNav.vue` (el `<button class="fab">`, ~línea 26)
- Modify: `resources/js/pwa/router/index.js`

- [ ] **Step 1: Gate del FAB por meta de ruta**

En `BottomNav.vue`, en el `<button type="button" class="fab" :class="{ 'fab--open': fabOpen }" …>` añade `v-if="!route.meta.hidePrimaryFab"` (la variable `route` ya existe: `const route = useRoute()`). Queda la apertura del botón así:
```html
    <button
        v-if="!route.meta.hidePrimaryFab"
        type="button"
        class="fab"
        :class="{ 'fab--open': fabOpen }"
        :aria-expanded="fabOpen.toString()"
```
(No toques el backdrop/sheet del FAB — solo el botón. Al ocultarse el botón, `fabOpen` no se abrirá en esas vistas.)

- [ ] **Step 2: Marcar las rutas**

En `resources/js/pwa/router/index.js`, en la ruta cuyo `component` es `CreditDetailView` (path `/pwa/credits/:id`) y en la de `CompanySettingsView`, añade `hidePrimaryFab: true` a su `meta`. Ejemplo para credit-detail:
```js
        meta: { requiresAuth: true, hidePrimaryFab: true },
```
Haz lo mismo en la ruta de `CompanySettingsView` (fusiona con su `meta` existente, sin quitar `requiresAuth`).

- [ ] **Step 3: Verificar build + que el gate está**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -2"
wsl bash -lc "cd /var/www/html/credify && grep -n 'hidePrimaryFab' resources/js/pwa/components/layout/BottomNav.vue resources/js/pwa/router/index.js"
```
Expected: build OK; `hidePrimaryFab` aparece 1 vez en BottomNav (el `v-if`) y 2 veces en el router (las 2 rutas).

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add resources/js/pwa/components/layout/BottomNav.vue resources/js/pwa/router/index.js && git commit -m 'feat(pwa): ocultar FAB en vistas con barra de accion propia (route.meta.hidePrimaryFab)'"
```

---

### Task 10: Verificación holística + CHANGELOG

**Files:**
- Modify: `CHANGELOG.md`

- [ ] **Step 1: Build final + barrido de residuos**

```bash
wsl bash -lc "cd /var/www/html/credify && npm run build 2>&1 | grep -iE 'error|built in' | tail -3"
wsl bash -lc "cd /var/www/html/credify && grep -rnE 'fixed inset-0 z-50' resources/js/pwa/components resources/js/pwa/views || echo 'OK: ningun overlay quedo en z-50'"
```
Expected: build OK; **ningún** `fixed inset-0 z-50` residual en overlays (los 4 modales migrados). (Nota: `z-50`/`z-10` en usos NO-overlay —headers, badges— pueden permanecer; el barrido es solo de `fixed inset-0 z-50`.)

- [ ] **Step 2: Revisión visual en el preview**

Con el dev server (`preview_start` name del PWA si existe, o servir el build), o revisando el DOM: abrir cada overlay y confirmar que su backdrop cubre barra + FAB y sus botones son accesibles. Como la PWA requiere login, dejar constancia de que el **check final en dispositivo lo hará el usuario** con los flujos reales (anular pago, "no paga hoy", quick actions, recibo, guardar ajustes de empresa, barra Registrar Pago).

- [ ] **Step 3: CHANGELOG**

Bajo `## [Sin publicar]` → `### Corregido` (créala si no existe), añade: corrección sistémica de stacking de overlays en la PWA — escala de z-index por tokens (`--z-overlay/modal/toast/page-cta` + `--pwa-nav-space`), nuevo `BaseSheet` compartido (teleport + z + padding que reserva el nav) al que migran los 4 modales (Anular pago, No-paga-hoy, Quick actions, Recibo), 2 barras CTA fijas elevadas por encima del nav, toasts a `--z-toast`, y el FAB oculto en vistas con barra de acción propia (`route.meta.hidePrimaryFab`). Arregla el modal de Anular pago cuyos botones quedaban tras la barra inferior. Ref al spec.

- [ ] **Step 4: Commit**

```bash
wsl bash -lc "cd /var/www/html/credify && git add CHANGELOG.md && git commit -m 'docs(pwa): CHANGELOG fix de stacking de overlays'"
```

---

## Deploy (post-merge, flujo establecido — **full**: build + assets)

Cambia el bundle de la PWA → tras merge a `main` y realineo de `dev`:
1. `npm run build` en WSL → `public/build` + `public/pwa-sw.js`.
2. scp a prod `/tmp/*.new` + swap atómico (`build.staging`→`build`, `chown www-data`). Ver [[credify-prod-vm]].
3. `git pull --ff-only origin main` + `php artisan optimize:clear && optimize` + `sudo -n systemctl reload php8.3-fpm`.
4. Verificación final en dispositivo por el usuario.

---

## Self-Review (cobertura vs. spec)

- **Escala aditiva por tokens (--z-page-cta/overlay/modal/toast + --pwa-nav-space):** Task 1. ✅
- **Componente BaseSheet (teleport + z + nav-safe + align end/center + Escape + scroll-lock):** Task 2. ✅
- **Migrar los 4 modales:** Tasks 3 (PaymentHistory/reportado), 4 (NoPayment), 5 (QuickAction), 6 (Receipt center). ✅
- **Arreglar las 2 barras CTA (CompanySettings, CreditDetail) por encima del nav:** Task 7. ✅
- **Toasts a --z-toast:** Task 8 (ToastContainer + 4 pills). ✅
- **Pulido del FAB (route.meta.hidePrimaryFab):** Task 9. ✅
- **No tocar la banda del nav (100–500) ni backend/Blade/web:** ningún task lo hace. ✅
- **Gotcha dark scoped CSS:** BaseSheet no usa selectores `:global(html.dark) .sel` (sus estilos son neutrales/rgba); si se añadiera dark, seguir [[credify-pwa-dark-scoped-css]]. ✅
- **Verificación build + device (sin tests JS):** cada task build + Task 10 holístico. ✅
- **Deploy full:** sección Deploy. ✅

**Placeholder scan:** las migraciones (Tasks 3-6) instruyen "conservar el contenido interno tal cual" citando el markup exacto a remover (wrapper `fixed inset-0 z-50` + backdrop) y el uso exacto de `<BaseSheet>` — es una transformación de refactor precisa, no un placeholder (el contenido a preservar ya existe en el archivo). Tokens y BaseSheet con código completo.

**Consistencia de tipos/nombres:** `BaseSheet` props `open`/`align`/`dismissible`, evento `update:open` — usados idénticos en Tasks 3-6. Tokens `--z-overlay`/`--z-modal`/`--z-toast`/`--z-page-cta`/`--pwa-nav-space` definidos en Task 1 y referenciados con esos nombres exactos en Tasks 2/7/8. `route.meta.hidePrimaryFab` consistente entre BottomNav (Task 9.1) y router (Task 9.2). ✅
