Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .csdd/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@

## Ready to Land

- [ ] T-043 — Corregir el flash de CTAs del Navbar al cargar el Hero (issue #25)
- Owner: Valen
- Agent: Codex
- Scope: `components/sections/Navbar.tsx`, `app/page.tsx`, `e2e/smoke.spec.ts`, `.csdd/todo.md`; no tocar `PrimaryCta` ni el glow del issue #26
- Target: `master`
- Updated: 2026-08-17
- Landing: [draft PR #28](https://github.com/ValenFelizia/787-Rumbos/pull/28) hacia `master`; sin merge manual del issue
- Verification: lint OK (1 warning preexistente en `Footer.tsx`), typecheck OK, build OK, smoke E2E 9/9 OK; QA visual desktop/mobile OK
- Note: el estado inicial del home se hace determinista con `isHome`, evitando depender de `usePathname()` durante render/hidratación. Se preservan transición geométrica, `aria-hidden`, `inert`, foco y CTAs mobile.

## Blocked

## Pending
Expand Down
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function Home() {
return (
<main className="min-h-screen bg-[#f9f9f9] text-[#0b4058]">
<SpecialPromo />
<Navbar />
<Navbar isHome />
<Hero />
<TrustBar />
<AboutUs />
Expand Down
22 changes: 14 additions & 8 deletions components/sections/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* components/sections/Navbar.tsx
*
* Barra sticky. En desktop, los CTAs se ocultan mientras #hero ocupa el viewport
* (T-028 / T-030) para no duplicar el par del Hero; al scrollear fuera reaparecen.
* (T-028 / T-030 / T-043) para no duplicar el par del Hero; al scrollear fuera
* reaparecen.
*/
import { useState, useEffect, useRef } from "react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Menu, X } from "lucide-react";
import { PrimaryCta, SecondaryCta } from "@/components/conversion";
import { useModal } from "@/lib/context/ModalContext";
Expand All @@ -34,21 +34,26 @@ function isHeroOccupyingViewport(): boolean {
return hero.getBoundingClientRect().bottom > NAV_CLEARANCE_PX;
}

export function Navbar() {
type NavbarProps = {
/** The home route needs deterministic SSR markup before client hydration. */
isHome?: boolean;
};

export function Navbar({ isHome = false }: NavbarProps) {
const { openModal } = useModal();
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);
// Home: arrancar ocultos (Hero ya lleva el par). Otras rutas: siempre visibles.
const [showDesktopCtas, setShowDesktopCtas] = useState(pathname !== "/");
// Keep this route decision server-deterministic to avoid a hydration flash.
const [showDesktopCtas, setShowDesktopCtas] = useState(!isHome);
const menuButtonRef = useRef<HTMLButtonElement>(null);
const menuDialogRef = useRef<HTMLDivElement>(null);
const closeMenuButtonRef = useRef<HTMLButtonElement>(null);
const desktopCtasRef = useRef<HTMLDivElement>(null);
const logoLinkRef = useRef<HTMLAnchorElement>(null);

// T-028: CTAs desktop siguen la geometría del Hero (sync + scroll/resize).
// T-028/T-043: CTAs desktop siguen la geometría del Hero (sync + scroll/resize).
useEffect(() => {
if (pathname !== "/") {
if (!isHome) {
setShowDesktopCtas(true);
return;
}
Expand All @@ -69,7 +74,7 @@ export function Navbar() {
window.removeEventListener("scroll", syncCtaVisibility);
window.removeEventListener("resize", syncCtaVisibility);
};
}, [pathname]);
}, [isHome]);

// Si los CTAs se ocultan con foco dentro, devolverlo al logo.
useEffect(() => {
Expand Down Expand Up @@ -190,6 +195,7 @@ export function Navbar() {
<div className="min-w-0 overflow-hidden">
<div
ref={desktopCtasRef}
data-testid="desktop-navbar-ctas"
className="flex items-center gap-2 pr-0.5"
aria-hidden={!showDesktopCtas}
{...(!showDesktopCtas ? { inert: true } : {})}
Expand Down
35 changes: 35 additions & 0 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,38 @@ test.describe("cotizador", () => {
).toBeVisible();
});
});

test.describe("navbar en el hero", () => {
test("mantiene los CTAs desktop ocultos desde el HTML inicial y al volver arriba", async ({ page }) => {
const response = await page.goto("/", { waitUntil: "domcontentloaded" });

expect(response).not.toBeNull();
const initialHtml = await response!.text();
const initialDesktopCtaMarkup = initialHtml.match(
/<div[^>]*data-testid="desktop-navbar-ctas"[^>]*>/
)?.[0];

expect(initialDesktopCtaMarkup).toContain('aria-hidden="true"');
expect(initialDesktopCtaMarkup).toContain('inert=""');

const nav = page.getByRole("navigation", { name: "Navegación principal" });
const navCtaGroup = nav.getByTestId("desktop-navbar-ctas");
const navCtaGrid = nav.locator("div.opacity-0").first();
const navCta = nav.locator('button[aria-label^="Armar viaje"]').first();

await expect(navCtaGroup).toHaveAttribute("aria-hidden", "true");
await expect(navCtaGroup).toHaveAttribute("inert", "");
await expect(navCtaGrid).toHaveCSS("opacity", "0");

await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await expect(navCta).toBeVisible();
await expect(navCtaGroup).toHaveAttribute("aria-hidden", "false");

await page.evaluate(() => window.scrollTo(0, 0));
await expect(navCtaGroup).toHaveAttribute("aria-hidden", "true");
await expect(navCtaGrid).toHaveCSS("opacity", "0");

await page.reload({ waitUntil: "domcontentloaded" });
await expect(nav.locator("div.opacity-0").first()).toHaveCSS("opacity", "0");
});
});
Loading