# Escala de z-index + modal compartido para overlays de la PWA

- **Fecha:** 2026-08-01
- **Rama:** `fix/pwa-overlay-zindex`
- **Estado:** diseño aprobado en brainstorming (escala aditiva + `BaseSheet` + migrar 7 overlays + arreglar 2 barras CTA + pulido del FAB), pendiente de plan.
- **Motivación:** en el modal de "Anular pago" (PWA) los botones quedan **tapados por la barra de navegación inferior** y un FAB verde "+" se cuela encima. Debugging sistemático (Fase 1) mostró que **no es un bug del modal** sino un problema sistémico de stacking en toda la PWA.

## Diagnóstico (causa raíz confirmada)

La PWA **no tiene una escala de z-index unificada ni un componente de modal compartido**. Se combinan 3 factores:

1. **Escalas en conflicto.** Los overlays de la app usan Tailwind `z-50`/`z-40`, pero `components/layout/BottomNav.vue` tiene una banda propia alta: barra **z-index 100** (`.tab-bar`), backdrop del FAB **200**, action-sheet del FAB **400** (ambos `Teleport` a `body`), y **FAB central 500** (`.fab`). Como `50 < 100 < 500`, el modal queda debajo.
2. **El FAB central (z-500) se renderiza SIEMPRE** (no está detrás de `v-if`; solo el backdrop/sheet lo están) → se cuela encima de cualquier overlay con `z < 500` en toda pantalla autenticada.
3. **Ningún `.safe-area-bottom` reserva el alto real del nav (~82px)** — solo `env(safe-area-inset-bottom)` (el notch). Los botones al fondo caen en la franja de la barra.

Además: **no existe** ningún `BaseModal`/`BaseSheet` ni escala documentada (`--z-*`), así que el bug reaparece con cada modal nuevo. Ningún modal está atrapado en un stacking-context ancestro (verificado) → un ajuste de z-index basta (no hace falta teleport forzado, aunque el componente lo usará por consistencia).

## Auditoría — overlays afectados (los 7)

| Componente | Qué es | Verdict |
|---|---|---|
| `views/CompanySettingsView.vue` (barra "Guardar Cambios", `fixed bottom-0` **sin z-index**) | barra CTA | 🔴 **Funcional** — el tap cae en una pestaña del nav en vez de en Guardar |
| `components/credits/PaymentHistory.vue` (modal Anular, `z-50`, no teleport, `items-end`) | modal | 🔴 (el reportado) |
| `components/collections/NoPaymentModal.vue` (`z-50`, teleport, `items-end`) | bottom-sheet | 🔴 uso diario del cobrador |
| `components/dashboard/QuickActionRow.vue` (`z-50`, teleport, `items-end`) | bottom-sheet | 🔴 en home de cada rol |
| `views/CreditDetailView.vue` (barra "No paga / Registrar Pago", sin z-index) | barra CTA | 🟠 el FAB recorta el botón derecho |
| `components/payments/PaymentReceipt.vue` (`z-50`, teleport, `items-center`) | modal centrado | 🟠 el FAB se cuela abajo |
| Toasts (`ui/ToastContainer.vue` + pills en varias vistas, `z-50`/`z-40`, `bottom-20/24`) | toasts | 🟡 cosmético (el FAB roza el borde) |

## Decisiones (del brainstorming)

- **Escala aditiva** (no renumerar): se conserva la banda del nav (100–500) intacta —el FAB a 500 actúa de "cerrar" sobre su propio sheet a 400— y se añaden tokens **por encima de 500** para overlays/modales/toasts + un tier para barras CTA de página.
- **Componente compartido `BaseSheet`** al que migran los 4 modales; barras CTA y toasts se arreglan vía tokens.
- **Pulido del FAB incluido:** ocultar el FAB central en vistas que ya tienen su propia barra de acción principal abajo (evita dos acciones flotantes compitiendo), vía `route.meta`.

## Diseño

### 1. Escala de z-index documentada

Tokens CSS en `resources/js/pwa/assets/pwa.css` (Tailwind v4 CSS-first; `:root` custom properties, referenciadas con `z-[var(--z-…)]` o utilidades):

```
--pwa-nav-space: calc(env(safe-area-inset-bottom) + 82px);  /* alto real del nav */

--z-header:    20;    /* headers sticky (ya ~z-20) */
/* nav (BottomNav) — EXISTENTE, sin tocar: barra 100, backdrop 200, sheet 400, FAB 500 */
--z-page-cta:  600;   /* barras de acción fijas de página (Guardar, Registrar Pago): por encima del FAB */
--z-overlay:   1000;  /* backdrop de modal/hoja */
--z-modal:     1010;  /* panel de modal/hoja */
--z-toast:     1100;  /* toasts, por encima de todo */
```

**Efecto clave:** el backdrop del modal a **1000 (>500)** tapa el FAB → segundo síntoma resuelto de raíz.

Utilidad de padding para reservar el nav: `.pb-nav-safe { padding-bottom: calc(var(--pwa-nav-space) + 1rem); }` (o equivalente), usada por el contenido de las hojas y por las barras CTA que se posicionan sobre el nav (`bottom: var(--pwa-nav-space)`).

### 2. Componente compartido `components/ui/BaseSheet.vue`

- **Teleport a `body`.** Backdrop full-screen a `--z-overlay` (dim + `@click.self` cierra si `dismissible`). Panel a `--z-modal`.
- **Props:** `open` (con `v-model:open`), `title?`, `align` (`'end' | 'center'`, default `'end'`), `dismissible` (default `true`).
- **Slots:** default (cuerpo), `#actions` (fila de botones), `#title?`.
- **Nav-safe:** el panel `align=end` reserva `--pwa-nav-space` en su padding inferior → los botones nunca caen en la franja del nav. `align=center` va centrado (sin problema de fondo).
- **Extras:** bloqueo de scroll del body al abrir (`overflow:hidden` en `html`/`#credify-pwa`), cierre con `Escape`, `role="dialog"` + `aria-modal`. Respeta el gotcha de dark scoped CSS del proyecto (usar `:global(html.dark .sel)`, no `:global(html.dark) .sel`) — ver [[credify-pwa-dark-scoped-css]].

### 3. Migración de los overlays

- **A `BaseSheet`:**
  - `PaymentHistory.vue` modal Anular → `<BaseSheet v-model:open align="end">` (el reportado).
  - `NoPaymentModal.vue` → `align="end"`.
  - `QuickActionRow.vue` → `align="end"`.
  - `PaymentReceipt.vue` → `align="center"`.
  Se elimina el markup hand-rolled (`fixed inset-0 z-50` + backdrop) de cada uno; el cuerpo y los botones pasan a los slots.
- **Barras CTA de página → token `--z-page-cta` + posición sobre el nav** (`bottom: var(--pwa-nav-space)`, `padding-bottom` seguro):
  - `CompanySettingsView.vue` (Guardar Cambios) — la peor (sin z-index).
  - `CreditDetailView.vue` (No paga / Registrar Pago).
- **Toasts → `--z-toast`:** `ui/ToastContainer.vue` y las pills en `ExpenseCreateView`, `PartnerTransactionView`, `ApprovalsView`, `CreditOrderView`.

### 4. Pulido del FAB (`BottomNav.vue`)

- El `<button class="fab">` se oculta con `v-if="!route.meta.hidePrimaryFab"` (o `v-show`). `route` ya está disponible (`useRoute()`).
- Se marca `meta: { hidePrimaryFab: true }` en las rutas de las vistas con barra de acción propia abajo (al menos `CreditDetailView` y `CompanySettingsView`; el plan enumera las que apliquen) en `resources/js/pwa/router/index.js`.
- Cuando hay un `BaseSheet` abierto, su backdrop (1000) ya cubre el FAB — el `meta` es para las vistas con **barra CTA** (no modal), donde no hay backdrop.

## No-objetivos / límites

- **No** renumerar la banda interna del nav (100–500) ni reescribir `BottomNav` — solo se le añade el `v-if` del FAB.
- **No** tocar backend, Blade, ni la web pública. Es solo la PWA (Vue).
- **No** rediseñar el look de los modales — se conserva su contenido y estilo (`.pwa-card`, etc.); solo cambia el contenedor/stacking.
- **No** migrar a `BaseSheet` los toasts ni las barras CTA (no son modales) — esos se arreglan con tokens.

## Riesgos / gotchas

- **Sin tests JS de la PWA** → verificación por `npm run build` + revisión visual en el preview del navegador; el check final en dispositivo lo hace el usuario (la PWA requiere login).
- **Dark scoped CSS** (`:global(html.dark) .sel` miscompila) — usar `:global(html.dark .sel)` en `BaseSheet`. Ver [[credify-pwa-dark-scoped-css]].
- **Tailwind v4 content-scan** — `z-[var(--z-modal)]` es utilidad arbitraria; verificar que compila (el scan usa `storage/framework/views/*.php` para Blade, pero la PWA es JS compilado por Vite, que sí ve los `.vue`). Confirmar tras build que las clases aparecen.
- **Regresión de otros overlays** al migrar — verificar cada uno de los 7 en el preview (abre, botones accesibles, FAB/nav no compiten).
- **Deploy full** (assets): cambia el bundle de la PWA → requiere `npm run build` + ship de `build`/`pwa-sw.js` a prod (ver [[credify-prod-vm]]).

## Verificación

- `npm run build` sin errores.
- Preview del navegador (o revisión de cada componente): los 4 modales aparecen **por encima** del nav, con botones accesibles y el backdrop cubriendo barra + FAB; las 2 barras CTA quedan sobre el nav y son tappables; el FAB desaparece en las vistas marcadas; toasts por encima.
- Verificación final en dispositivo por el usuario (login + flujos reales: anular pago, "no paga hoy", quick actions, recibo, guardar ajustes).
- CHANGELOG.
