Compare commits

...
10 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 d17407a302 fix: landing page completamente responsive en moracourier.com
ci-337446 / run (push) Has been cancelled
ci-377635 / run (push) Has been cancelled
Build & Push Docker images / build-api (push) Has been cancelled
Build & Push Docker images / build-web (push) Has been cancelled
- Reemplaza todos los inline styles con clases CSS responsivas
- Agrega 230 líneas de CSS landing-específico con breakpoints 768px y 1024px
- Grids adaptivos: 4-col→2-col→1-col, 3-col→2-col→1-col, 2-col→1-col
- Nuevo componente LandingNavbar (client) con hamburger funcional y menú móvil
- Hero, Servicios, Módulos, Tracking, Calculadora, Roles y Empresas responsivos
- Agrega sección "Cómo funciona" (4 pasos) que faltaba
- Agrega sección CTA final antes del footer
- Footer con links de navegación completos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 12:21:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 492dc5436c fix: auditoría UI/UX landing page y consistencia entre subpáginas
- Agrega menú móvil funcional con panel deslizable y animación hamburger
- Corrige hero stat "150 N / Day St" confuso por "$3.50/lb (flete)"
- Arregla bug CSS `flex-wrap: gap` inválido en footer-bottom
- Corrige links "Carga Pesada" en nav y footer para apuntar a #cotizacion
- Agrega acción al botón de pre-alerta con validación
- Limpia address del footer (eliminado placeholder "Suite EC-" vacío)
- Agrega favicon, OG tags y theme-color a las 4 páginas
- Consolida event listeners para evitar conflictos móvil/WhatsApp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 11:35:15 -05:00
Lizandro Guarnizo 696ff9fa51 fix: add coolify external network to api/web for internal DB access 2026-06-25 20:55:47 -05:00
Lizandro Guarnizo 2985433ecb fix: add HOSTNAME=0.0.0.0 and fix web health check 2026-06-25 16:33:36 -05:00
Lizandro Guarnizo e76d7b5027 fix: use 127.0.0.1 instead of localhost in health check 2026-06-25 16:27:40 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 40e378bff8 fix: create uploads dir with correct ownership before dropping to app user
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 00:35:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d9062ce754 fix: copy .prisma to correct pnpm versioned path in Docker runner stage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 00:32:19 -05:00
Lizandro Guarnizo 2587826fa6 fix(api): copy @prisma/client package from pnpm store to deploy 2026-06-23 00:22:44 -05:00
Lizandro Guarnizo b7db0d6741 fix(api): pnpm deploy + copy prisma generated client 2026-06-23 00:13:14 -05:00
Lizandro Guarnizo 34b4c2d6d2 fix(api): flatten pnpm symlinks with cp -rL for docker COPY 2026-06-23 00:05:29 -05:00
9 changed files with 648 additions and 303 deletions
+8 -1
View File
@@ -7,15 +7,22 @@ COPY . .
RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install --no-frozen-lockfile --force RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install --no-frozen-lockfile --force
RUN pnpm --filter @moraworld/database exec prisma generate && pnpm --filter @moraworld/database build RUN pnpm --filter @moraworld/database exec prisma generate && pnpm --filter @moraworld/database build
RUN pnpm --filter @moraworld/api build RUN pnpm --filter @moraworld/api build
RUN pnpm deploy --filter @moraworld/api --prod /app/deploy
# pnpm deploy no incluye .prisma (binario generado) — copiarlo al mismo path relativo que usa pnpm
RUN src=$(find /app/node_modules/.pnpm -name ".prisma" -path "*@prisma+client*" -type d | head -1) && \
rel="${src#/app/node_modules/}" && \
mkdir -p "/app/deploy/node_modules/$(dirname "$rel")" && \
cp -rL "$src" "/app/deploy/node_modules/$(dirname "$rel")/"
FROM node:22-alpine AS runner FROM node:22-alpine AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
RUN addgroup -S app && adduser -S app app RUN addgroup -S app && adduser -S app app
COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/deploy/node_modules ./node_modules
COPY --from=builder /app/apps/api/dist ./dist COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/apps/api/package.json ./ COPY --from=builder /app/apps/api/package.json ./
COPY --from=builder /app/packages/database ./packages/database COPY --from=builder /app/packages/database ./packages/database
RUN mkdir -p /app/uploads && chown -R app:app /app
USER app USER app
EXPOSE 3001 EXPOSE 3001
CMD ["node", "dist/main.js"] CMD ["node", "dist/main.js"]
@@ -0,0 +1,74 @@
"use client";
import { useState, useEffect, useRef } from "react";
import Link from "next/link";
export function LandingNavbar() {
const [open, setOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const btnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
function handleClick(e: MouseEvent) {
if (
menuRef.current && !menuRef.current.contains(e.target as Node) &&
btnRef.current && !btnRef.current.contains(e.target as Node)
) {
setOpen(false);
}
}
document.addEventListener("click", handleClick);
return () => document.removeEventListener("click", handleClick);
}, []);
function close() { setOpen(false); }
return (
<>
<nav className="ln-nav">
<div className="ln-inner container">
<Link href="/" className="ln-logo">
Moraworld<span>.</span>Imports
</Link>
<div className="ln-links">
<Link href="/calculadora" className="ln-link">Calculadora</Link>
<Link href="/tracking" className="ln-link">Tracking</Link>
<Link href="/como-funciona" className="ln-link">Cómo funciona</Link>
<Link href="/carga-pesada" className="ln-link">Carga Pesada</Link>
<Link href="/quienes-somos" className="ln-link">Nosotros</Link>
</div>
<div className="ln-cta">
<Link href="/login" className="btn btn-outline btn-sm">Iniciar sesión</Link>
<Link href="/registro" className="btn btn-primary btn-sm">Abrir casillero</Link>
</div>
<button
ref={btnRef}
className={`ln-hamburger${open ? " open" : ""}`}
onClick={() => setOpen(v => !v)}
aria-label="Abrir menú"
>
<span />
<span />
<span />
</button>
</div>
</nav>
<div ref={menuRef} className={`ln-mobile-menu${open ? " open" : ""}`}>
<div className="ln-mobile-links">
<Link href="/calculadora" onClick={close}>Calculadora</Link>
<Link href="/tracking" onClick={close}>Tracking</Link>
<Link href="/como-funciona" onClick={close}>Cómo funciona</Link>
<Link href="/carga-pesada" onClick={close}>Carga Pesada</Link>
<Link href="/quienes-somos" onClick={close}>Nosotros</Link>
</div>
<div className="ln-mobile-cta">
<Link href="/login" className="btn btn-outline w-full" onClick={close}>Iniciar sesión</Link>
<Link href="/registro" className="btn btn-primary w-full" onClick={close}>Abrir casillero gratis </Link>
</div>
</div>
</>
);
}
+230
View File
@@ -472,3 +472,233 @@ tbody tr:hover { background: var(--gray-50); }
.grid-4 { grid-template-columns: repeat(2,1fr); } .grid-4 { grid-template-columns: repeat(2,1fr); }
.grid-3 { grid-template-columns: repeat(2,1fr); } .grid-3 { grid-template-columns: repeat(2,1fr); }
} }
/* ═══════════════════════════════════════════════════════
LANDING PAGE — Responsive Layout
═══════════════════════════════════════════════════════ */
/* ── Navbar ── */
.ln-nav {
position: sticky; top: 0; z-index: 50;
background: rgba(255,255,255,.97); backdrop-filter: blur(12px);
border-bottom: 1px solid var(--gray-200);
height: 64px; display: flex; align-items: center;
}
.ln-inner {
display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.ln-logo {
font-size: 1.2rem; font-weight: 800; color: var(--dark); white-space: nowrap; flex-shrink: 0;
}
.ln-logo span { color: var(--accent); }
.ln-links {
display: flex; align-items: center; gap: 1.5rem; flex: 1; justify-content: center;
}
.ln-link {
font-size: .875rem; font-weight: 500; color: var(--gray-700); transition: color .15s; white-space: nowrap;
}
.ln-link:hover { color: var(--primary); }
.ln-cta { display: flex; gap: .5rem; flex-shrink: 0; }
.ln-hamburger {
display: none; flex-direction: column; gap: 5px;
background: none; border: none; padding: 4px; cursor: pointer; flex-shrink: 0;
}
.ln-hamburger span {
display: block; width: 24px; height: 2px;
background: var(--gray-900); border-radius: 2px; transition: all .3s;
}
.ln-hamburger.open span:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
.ln-hamburger.open span:nth-child(2) { opacity: 0; }
.ln-hamburger.open span:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }
/* ── Mobile menu ── */
.ln-mobile-menu {
display: none; position: fixed; top: 64px; left: 0; right: 0; z-index: 49;
background: var(--white); border-bottom: 1px solid var(--gray-200);
padding: 1rem 1.5rem 1.5rem; box-shadow: 0 8px 32px rgba(0,0,0,.12);
}
.ln-mobile-menu.open { display: block; }
.ln-mobile-links { display: flex; flex-direction: column; }
.ln-mobile-links a {
display: block; padding: .85rem 0; border-bottom: 1px solid var(--gray-100);
font-size: .95rem; font-weight: 500; color: var(--gray-700);
}
.ln-mobile-links a:last-child { border-bottom: none; }
.ln-mobile-links a:hover { color: var(--primary); }
.ln-mobile-cta { display: flex; flex-direction: column; gap: .5rem; margin-top: 1rem; }
/* ── Hero ── */
.ln-hero {
background: linear-gradient(135deg, #0D1117 0%, #0D2150 100%);
padding: 6rem 0 4.5rem; color: var(--white);
}
.ln-hero-badge {
display: inline-flex; align-items: center; gap: .5rem;
background: rgba(0,87,255,.15); border: 1px solid rgba(0,87,255,.3);
color: #7AADFF; font-size: .8rem; font-weight: 600;
padding: 6px 14px; border-radius: 999px; margin-bottom: 1.5rem;
}
.ln-hero-badge-dot {
width: 8px; height: 8px; border-radius: 50%; background: #7AADFF;
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot { 0%,100%{opacity:1} 50%{opacity:.4} }
.ln-hero h1 {
font-size: clamp(2rem, 5vw, 3.2rem); font-weight: 800;
line-height: 1.15; margin-bottom: 1.25rem;
}
.ln-hero h1 em { color: var(--accent); font-style: normal; }
.ln-hero-sub {
color: #9CA3AF; font-size: 1.05rem; max-width: 540px; margin-bottom: 1rem; line-height: 1.7;
}
.ln-hero-address {
background: rgba(0,87,255,.1); border: 1px solid rgba(0,87,255,.25);
border-radius: 10px; padding: .875rem 1.25rem; margin-bottom: 2rem;
font-family: monospace; font-size: .9rem; color: #A5B4FC; max-width: 520px;
}
.ln-hero-actions { display: flex; gap: .75rem; flex-wrap: wrap; margin-bottom: 3rem; }
.ln-hero-trust { display: flex; gap: 2rem; flex-wrap: wrap; }
.ln-trust-item { display: flex; gap: .625rem; align-items: flex-start; }
.ln-trust-icon { font-size: 1.4rem; }
.ln-trust-name { font-weight: 600; font-size: .875rem; color: var(--white); display: block; }
.ln-trust-sub { font-size: .75rem; color: #6B7280; }
/* ── Section wrappers ── */
.ln-section { padding: 5rem 0; }
.ln-section-gray { padding: 5rem 0; background: var(--gray-100); }
.ln-section-dark { padding: 5rem 0; background: var(--dark); }
.ln-section-head { text-align: center; margin-bottom: 3rem; }
.ln-section-tag {
display: inline-block; background: #EEF3FF; color: var(--primary);
font-size: .72rem; font-weight: 700; letter-spacing: .08em;
text-transform: uppercase; padding: 4px 14px; border-radius: 999px; margin-bottom: 1rem;
}
.ln-section-title { font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 800; margin-bottom: .5rem; }
.ln-section-sub { font-size: .95rem; color: var(--gray-500); max-width: 560px; margin: 0 auto; }
/* ── Responsive grids (landing specific) ── */
.ln-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.ln-grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: 1rem; }
.ln-grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 1rem; }
.ln-grid-2-1 { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: start; }
/* ── Service cards ── */
.ln-service-card {
background: var(--white); border-radius: 16px; padding: 2rem;
border: 2px solid var(--gray-200); transition: border-color .25s, box-shadow .25s, transform .25s;
}
.ln-service-card:hover { border-color: var(--primary); box-shadow: var(--shadow-xl); transform: translateY(-4px); }
.ln-service-card.featured { border-color: #BFDBFE; }
.ln-service-icon { font-size: 2.5rem; margin-bottom: .75rem; }
.ln-service-card h3 { font-weight: 800; font-size: 1.15rem; margin-bottom: .5rem; }
.ln-service-card p { color: var(--gray-500); margin-bottom: 1rem; font-size: .88rem; }
.ln-service-list { padding-left: 1.25rem; display: flex; flex-direction: column; gap: 5px; font-size: .85rem; color: var(--gray-700); margin-bottom: 1.25rem; }
/* ── Module cards ── */
.ln-module-card {
background: var(--gray-50); border-radius: 12px; padding: 1.25rem;
border: 1px solid var(--gray-200); transition: box-shadow .2s, transform .2s;
}
.ln-module-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.ln-module-icon { font-size: 1.8rem; margin-bottom: .625rem; }
.ln-module-title { font-weight: 700; font-size: .9rem; margin-bottom: .375rem; }
.ln-module-desc { color: var(--gray-500); font-size: .8rem; margin-bottom: .625rem; line-height: 1.5; }
.ln-module-tag { background: #EEF3FF; color: var(--primary); font-size: .68rem; font-weight: 700; padding: 2px 8px; border-radius: 999px; }
/* ── Tracking states ── */
.ln-tracking-state {
display: flex; gap: .75rem; align-items: center;
background: var(--white); border-radius: 8px; padding: .5rem .875rem;
border: 1px solid var(--gray-200);
}
.ln-state-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.ln-state-num { font-family: monospace; font-size: .68rem; color: #9CA3AF; min-width: 20px; }
.ln-state-name { font-family: monospace; font-size: .78rem; font-weight: 700; flex: 1; }
.ln-state-desc { font-size: .72rem; color: var(--gray-500); text-align: right; }
/* ── Calculator preview ── */
.ln-calc-formula {
background: var(--dark); border-radius: 12px; padding: 1.25rem 1.5rem;
font-family: monospace; font-size: .82rem; color: #A5B4FC;
text-align: left; max-width: 600px; margin: 0 auto 1.75rem;
line-height: 2; border: 1px solid rgba(255,255,255,.08);
}
.ln-senae-card {
border-radius: 12px; padding: 1.5rem; text-align: center;
}
.ln-senae-rate { font-size: 2rem; font-weight: 900; line-height: 1; margin: .25rem 0 .5rem; }
/* ── Role cards ── */
.ln-role-card {
border-radius: 12px; padding: 1.25rem; border: 1px solid var(--gray-200);
transition: box-shadow .2s, transform .2s;
}
.ln-role-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.ln-role-icon { font-size: 1.5rem; margin-bottom: .625rem; }
.ln-role-name { font-weight: 700; margin-bottom: .375rem; font-size: .95rem; }
.ln-role-desc { color: var(--gray-500); font-size: .82rem; margin-bottom: .75rem; line-height: 1.5; }
.ln-role-badge { background: rgba(0,0,0,.06); font-size: .7rem; font-weight: 700; padding: 2px 10px; border-radius: 999px; }
/* ── Company cards ── */
.ln-company-card {
background: var(--white); border-radius: 16px; padding: 1.75rem;
border: 2px solid var(--gray-200); display: flex; gap: 1rem; align-items: flex-start;
transition: border-color .25s;
}
.ln-company-card:hover { border-color: var(--primary); }
.ln-company-flag { font-size: 2.5rem; flex-shrink: 0; }
.ln-company-name { font-weight: 800; margin-bottom: .375rem; }
.ln-company-desc { color: var(--gray-500); font-size: .82rem; line-height: 1.6; margin-bottom: .625rem; }
.ln-company-badge { background: #EEF3FF; color: var(--primary); font-size: .7rem; font-weight: 700; padding: 2px 10px; border-radius: 999px; }
/* ── CTA final ── */
.ln-cta-section {
padding: 5rem 0;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
text-align: center;
}
.ln-cta-section h2 { font-size: clamp(1.6rem, 4vw, 2.5rem); font-weight: 800; color: var(--white); margin-bottom: 1rem; }
.ln-cta-section p { color: rgba(255,255,255,.75); font-size: 1rem; margin-bottom: 2rem; }
/* ── Footer ── */
.ln-footer { background: var(--dark); padding: 3.5rem 0 2rem; color: rgba(255,255,255,.5); }
.ln-footer-logo { font-size: 1.1rem; font-weight: 800; color: var(--white); margin-bottom: .5rem; }
.ln-footer-logo span { color: var(--accent); }
.ln-footer-links {
display: flex; gap: 1.5rem; flex-wrap: wrap; justify-content: center;
margin: 1.5rem 0; font-size: .82rem;
}
.ln-footer-links a:hover { color: var(--white); }
.ln-footer-badges { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; margin-bottom: 1.5rem; }
.ln-footer-badge {
background: rgba(255,255,255,.07); color: rgba(255,255,255,.5);
font-size: .7rem; font-weight: 700; padding: 4px 12px; border-radius: 999px;
border: 1px solid rgba(255,255,255,.1);
}
.ln-footer-legal {
border-top: 1px solid rgba(255,255,255,.07); margin-top: 1.5rem; padding-top: 1rem;
font-size: .72rem; color: rgba(255,255,255,.3); line-height: 1.7; text-align: center;
}
/* ─── Responsive breakpoints (landing) ─────────────────── */
@media (max-width: 1024px) {
.ln-grid-4 { grid-template-columns: repeat(2,1fr); }
.ln-grid-3 { grid-template-columns: repeat(2,1fr); }
}
@media (max-width: 768px) {
.ln-links, .ln-cta { display: none !important; }
.ln-hamburger { display: flex; }
.ln-grid-2, .ln-grid-3, .ln-grid-4, .ln-grid-2-1 { grid-template-columns: 1fr !important; }
.ln-hero { padding: 3rem 0 2.5rem; }
.ln-section, .ln-section-gray, .ln-section-dark { padding: 3rem 0; }
.ln-cta-section { padding: 3rem 0; }
.ln-hero-trust { gap: 1.25rem; }
.ln-state-desc { display: none; }
.ln-tracking-state { flex-wrap: wrap; }
.ln-state-name { font-size: .72rem; }
}
+241 -276
View File
@@ -1,6 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { WhatsAppFloat } from "./_components/whatsapp-float"; import { LandingNavbar } from "./_components/landing-navbar";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Moraworld Imports — Tu casillero en New Jersey para Ecuador", title: "Moraworld Imports — Tu casillero en New Jersey para Ecuador",
@@ -9,133 +9,98 @@ export const metadata: Metadata = {
keywords: ["casillero New Jersey", "importaciones Ecuador", "SENAE", "Amazon Ecuador", "envíos EE.UU. Ecuador"], keywords: ["casillero New Jersey", "importaciones Ecuador", "SENAE", "Amazon Ecuador", "envíos EE.UU. Ecuador"],
}; };
// ─── Datos estáticos de la landing (sin DB, SSG) ────────────── // ─── Datos estáticos (SSG) ──────────────────────────────
const TRACKING_STATES = [ const TRACKING_STATES = [
{ num: "01", name: "REGISTRADO", color: "#6B7280", desc: "El cliente registró la compra" }, { num: "01", name: "REGISTRADO", color: "#6B7280", desc: "El cliente registró la compra" },
{ num: "02", name: "EN_TRANSITO_BODEGA", color: "#F59E0B", desc: "El vendedor despachó hacia bodega NJ" }, { num: "02", name: "EN_TRANSITO_BODEGA", color: "#F59E0B", desc: "Vendedor despachó a bodega NJ" },
{ num: "03", name: "RECIBIDO_BODEGA", color: "#3B82F6", desc: "Bodega NJ confirmó recepción" }, { num: "03", name: "RECIBIDO_BODEGA", color: "#3B82F6", desc: "Bodega NJ confirmó recepción" },
{ num: "04", name: "EN_VERIFICACION", color: "#8B5CF6", desc: "Pesando, midiendo y fotografiando" }, { num: "04", name: "EN_VERIFICACION", color: "#8B5CF6", desc: "Pesando, midiendo y fotografiando" },
{ num: "05", name: "VERIFICADO", color: "#10B981", desc: "Peso confirmado. Cobro aplicado." }, { num: "05", name: "VERIFICADO", color: "#10B981", desc: "Peso confirmado. Cobro aplicado." },
{ num: "06", name: "DECLARACION_ADUANERA", color: "#0057FF", desc: "DSI aprobada por la SENAE" }, { num: "06", name: "DECLARACION_SENAE", color: "#0057FF", desc: "DSI aprobada por la SENAE" },
{ num: "07", name: "EN_TRANSITO_ECUADOR", color: "#F97316", desc: "En vuelo hacia Ecuador" }, { num: "07", name: "EN_TRANSITO_EC", color: "#F97316", desc: "En vuelo hacia Ecuador" },
{ num: "08", name: "EN_ADUANA_ECUADOR", color: "#EF4444", desc: "Inspección en aduana Ecuador" }, { num: "08", name: "EN_ADUANA_EC", color: "#EF4444", desc: "Inspección en aduana Ecuador" },
{ num: "09", name: "LISTO_ENTREGA", color: "#84CC16", desc: "Listo para entrega al cliente" }, { num: "09", name: "LISTO_ENTREGA", color: "#84CC16", desc: "Listo para entrega al cliente" },
{ num: "10", name: "ENTREGADO", color: "#10B981", desc: "Paquete entregado en Ecuador ✓" }, { num: "10", name: "ENTREGADO", color: "#10B981", desc: "Paquete entregado en Ecuador ✓" },
{ num: "11", name: "INCIDENCIA", color: "#EF4444", desc: "Problema reportado ⚠" }, { num: "11", name: "INCIDENCIA", color: "#EF4444", desc: "Problema reportado ⚠" },
]; ];
const SENAE_CATEGORIES = [ const SENAE_CATEGORIES = [
{ name: "Régimen 4×4", rate: "0%", color: "#10B981", bg: "#D1FAE5", desc: "Hasta 4 envíos/año · máx. $400 · máx. 4 kg · Sin arancel" }, { name: "Régimen 4×4", rate: "0%", color: "#10B981", bg: "#D1FAE5", desc: "Hasta 4 envíos/año · máx. $400 · máx. 4 kg · Sin arancel" },
{ name: "Categoría B", rate: "10%", color: "#1D4ED8", bg: "#DBEAFE", desc: "Bienes de consumo general" }, { name: "Categoría B", rate: "10%", color: "#1D4ED8", bg: "#DBEAFE", desc: "Bienes de consumo general" },
{ name: "Categoría C", rate: "20%", color: "#FF6B00", bg: "#FFEDD5", desc: "Textiles, calzado, hogar" }, { name: "Categoría C", rate: "20%", color: "#C2410C", bg: "#FFEDD5", desc: "Textiles, calzado, hogar" },
{ name: "Categoría D", rate: "015%", color: "#8B5CF6", bg: "#EDE9FE", desc: "Electrónicos · puede requerir INEN" }, { name: "Categoría D", rate: "015%", color: "#7C3AED", bg: "#EDE9FE", desc: "Electrónicos · puede requerir INEN" },
]; ];
const MODULES = [ const MODULES = [
{ icon: "🌐", title: "Landing Pública", desc: "Calculadora SENAE, tracking sin login, tarifas y FAQ", tag: "Sin registro" }, { icon: "🌐", title: "Landing Pública", desc: "Calculadora SENAE, tracking sin login, tarifas y FAQ", tag: "Sin registro" },
{ icon: "👤", title: "Portal Cliente", desc: "Suite personalizada, pre-alertas, mis paquetes, historial", tag: "B2C · Ecuador" }, { icon: "👤", title: "Portal Cliente", desc: "Suite personalizada, pre-alertas, mis paquetes, historial", tag: "B2C · Ecuador" },
{ icon: "🏭", title: "Portal Bodega NJ", desc: "Recepción, peso, fotos, despacho desde 150 N Day St", tag: "Operador · NJ" }, { icon: "🏭", title: "Portal Bodega NJ", desc: "Recepción, peso, fotos y despacho desde 150 N Day St", tag: "Operador · NJ" },
{ icon: "⚙️", title: "Portal Admin", desc: "Dashboard, usuarios, tarifas, reportes, auditoría", tag: "Admin / Super Admin" }, { icon: "⚙️", title: "Portal Admin", desc: "Dashboard, usuarios, tarifas, reportes, auditoría", tag: "Admin" },
{ icon: "🛃", title: "Módulo SENAE", desc: "DSI automática, cálculo FODINFA+IVA+Arancel, WebService", tag: "Agente Aduanero" }, { icon: "🛃", title: "Módulo SENAE", desc: "DSI automática, cálculo FODINFA+IVA+Arancel, WebService", tag: "Agente Aduanero" },
{ icon: "📍", title: "Tracking", desc: "ID único EC-YYYYMMDD-XXXXXX · 11 estados · búsqueda pública", tag: "Todos los roles" }, { icon: "📍", title: "Tracking", desc: "ID único EC-YYYYMMDD-XXXXXX · 11 estados · búsqueda pública", tag: "Todos los roles" },
{ icon: "📄", title: "Pre-alerta", desc: "Aviso previo con factura antes de llegada a bodega", tag: "Cliente" }, { icon: "📄", title: "Pre-alerta", desc: "Aviso previo con factura antes de llegada a bodega", tag: "Cliente" },
{ icon: "🚢", title: "Carga Pesada B2B", desc: "Pallets, contenedores, cotización especial, INEN", tag: "Importadores" }, { icon: "🚢", title: "Carga Pesada B2B", desc: "Pallets, contenedores, cotización especial, INEN", tag: "Importadores" },
]; ];
const ROLES = [ const ROLES = [
{ icon: "👑", name: "Super Admin", desc: "Acceso total. Gestiona tenants y config del sistema.", badge: "Acceso total", bg: "#EEF3FF" }, { icon: "👑", name: "Super Admin", desc: "Acceso total. Gestiona tenants y config del sistema.", badge: "Acceso total", bg: "#EEF3FF" },
{ icon: "🏢", name: "Admin Empresa", desc: "Administra su tenant: usuarios, tarifas, reportes.", badge: "Tenant propio", bg: "#EDE9FE" }, { icon: "🏢", name: "Admin Empresa", desc: "Administra su tenant: usuarios, tarifas, reportes.", badge: "Tenant propio", bg: "#EDE9FE" },
{ icon: "📦", name: "Operador Bodega", desc: "Personal en NJ: verifica, pesa, fotografía y despacha.", badge: "Bodega NJ", bg: "#FEF3C7" }, { icon: "📦", name: "Operador Bodega", desc: "Personal en NJ: verifica, pesa, fotografía y despacha.", badge: "Bodega NJ", bg: "#FEF3C7" },
{ icon: "🛃", name: "Agente Aduanero", desc: "Revisa y aprueba declaraciones ante la SENAE.", badge: "SENAE", bg: "#D1FAE5" }, { icon: "🛃", name: "Agente Aduanero", desc: "Revisa y aprueba declaraciones ante la SENAE.", badge: "SENAE", bg: "#D1FAE5" },
{ icon: "🧑‍💻", name: "Cliente Final", desc: "Registra compras, pre-alertas y hace seguimiento.", badge: "B2C · Ecuador", bg: "#FFF3E8" }, { icon: "🧑‍💻", name: "Cliente Final", desc: "Registra compras, pre-alertas y hace seguimiento.", badge: "B2C · Ecuador", bg: "#FFF3E8" },
{ icon: "🎧", name: "Soporte", desc: "Solo lectura: puede ver info de clientes y paquetes.", badge: "Solo lectura", bg: "#F3F4F6" }, { icon: "🎧", name: "Soporte", desc: "Solo lectura: puede ver info de clientes y paquetes.", badge: "Solo lectura", bg: "#F3F4F6" },
]; ];
// ─── Componentes ────────────────────────────────────────────── // ─── Secciones ──────────────────────────────────────────
function Navbar() {
return (
<nav style={{
position: "sticky", top: 0, zIndex: 50,
background: "rgba(255,255,255,0.95)",
backdropFilter: "blur(12px)",
borderBottom: "1px solid #E5E7EB",
display: "flex", alignItems: "center", justifyContent: "space-between",
padding: "0 40px", height: 60,
}}>
<span style={{ fontWeight: 800, fontSize: "1.15rem", color: "#111827" }}>
Moraworld<span style={{ color: "#FF6B00" }}>.</span>Imports
</span>
<div style={{ display: "flex", gap: 24, alignItems: "center", fontSize: ".9rem" }}>
<Link href="/calculadora" style={{ color: "#4B5563" }}>Calculadora</Link>
<Link href="/tracking" style={{ color: "#4B5563" }}>Tracking</Link>
<Link href="/carga-pesada" style={{ color: "#4B5563" }}>Carga Pesada</Link>
<Link href="/portal" style={{
background: "#0057FF", color: "#fff",
padding: "8px 20px", borderRadius: 8, fontWeight: 600,
}}>Mi Portal</Link>
</div>
</nav>
);
}
function Hero() { function Hero() {
return ( return (
<section style={{ <section className="ln-hero">
background: "linear-gradient(135deg, #0D1117 0%, #0D2150 100%)", <div className="container">
padding: "100px 40px", color: "#fff", <div className="ln-hero-badge">
}}> <span className="ln-hero-badge-dot" />
<div style={{ maxWidth: 1100, margin: "0 auto" }}>
<div style={{
display: "inline-block",
background: "rgba(0,87,255,.15)", border: "1px solid rgba(0,87,255,.3)",
color: "#7AADFF", fontSize: ".8rem", fontWeight: 600,
padding: "6px 14px", borderRadius: 999, marginBottom: 24,
}}>
ISO/IEC 27001 · SENAE Autorizado · Cuenca, Ecuador ISO/IEC 27001 · SENAE Autorizado · Cuenca, Ecuador
</div> </div>
<h1 style={{ fontSize: "clamp(2rem,5vw,3.2rem)", fontWeight: 800, lineHeight: 1.15, marginBottom: 20 }}>
Tu casillero en <span style={{ color: "#FF6B00" }}>New Jersey</span> <h1>
Tu casillero en <em>New Jersey</em>
<br />para recibir en Ecuador <br />para recibir en Ecuador
</h1> </h1>
<p style={{ color: "#9CA3AF", maxWidth: 560, fontSize: "1.05rem", marginBottom: 16 }}>
<p className="ln-hero-sub">
Compra en Amazon, eBay, Walmart y más. Te asignamos una Suite personal en nuestra Compra en Amazon, eBay, Walmart y más. Te asignamos una Suite personal en nuestra
bodega en NJ. Gestionamos el envío y la declaración aduanera SENAE por ti. bodega en NJ. Gestionamos el envío y la declaración aduanera SENAE por ti.
</p> </p>
<div style={{
background: "rgba(0,87,255,.1)", border: "1px solid rgba(0,87,255,.25)", <div className="ln-hero-address">
borderRadius: 10, padding: "14px 18px", marginBottom: 32,
fontFamily: "monospace", fontSize: ".9rem", color: "#A5B4FC", maxWidth: 520,
}}>
📍 <strong style={{ color: "#FF6B00" }}>Tu Suite:</strong>{" "} 📍 <strong style={{ color: "#FF6B00" }}>Tu Suite:</strong>{" "}
150 N Day St, Suite EC-XXXXX, City of Orange, NJ 07050, EE.UU. 150 N Day St, Suite EC-XXXXX, City of Orange, NJ 07050, EE.UU.
</div> </div>
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
<Link href="/registro" style={{ <div className="ln-hero-actions">
background: "#0057FF", color: "#fff", <Link href="/registro" className="btn btn-primary btn-lg">
padding: "14px 28px", borderRadius: 8, fontWeight: 700, fontSize: ".95rem",
}}>
Crear mi Suite gratis Crear mi Suite gratis
</Link> </Link>
<Link href="/calculadora" style={{ <Link href="/calculadora" style={{
border: "2px solid rgba(255,255,255,.2)", color: "#9CA3AF", border: "2px solid rgba(255,255,255,.2)", color: "#9CA3AF",
padding: "12px 26px", borderRadius: 8, fontSize: ".9rem", padding: "12px 24px", borderRadius: 8, fontSize: ".9rem", display: "inline-flex", alignItems: "center",
}}> }}>
Calcular mi envío Calcular mi envío
</Link> </Link>
</div> </div>
<div style={{ display: "flex", gap: 32, marginTop: 48, flexWrap: "wrap" }}>
<div className="ln-hero-trust">
{[ {[
["🏢", "Moraworld Imports S.A.S.", "Ecuador · RUC activo"], ["🏢", "Moraworld Imports S.A.S.", "Ecuador · RUC activo"],
["🏭", "Mora Global Import LLC", "New Jersey, EE.UU."], ["🏭", "Mora Global Import LLC", "New Jersey, EE.UU."],
["🛃", "Autorización SENAE", "Declaración automática"], ["🛃", "Autorización SENAE", "Declaración automática"],
].map(([icon, title, sub]) => ( ].map(([icon, name, sub]) => (
<div key={title} style={{ display: "flex", gap: 10, alignItems: "flex-start" }}> <div key={name} className="ln-trust-item">
<span style={{ fontSize: "1.5rem" }}>{icon}</span> <span className="ln-trust-icon">{icon}</span>
<div> <div>
<div style={{ fontWeight: 600, fontSize: ".88rem", color: "#fff" }}>{title}</div> <span className="ln-trust-name">{name}</span>
<div style={{ fontSize: ".78rem", color: "#6B7280" }}>{sub}</div> <span className="ln-trust-sub">{sub}</span>
</div> </div>
</div> </div>
))} ))}
@@ -147,48 +112,33 @@ function Hero() {
function ServicesSection() { function ServicesSection() {
return ( return (
<section style={{ padding: "80px 40px", background: "#F3F4F6" }}> <section className="ln-section-gray">
<div style={{ maxWidth: 1100, margin: "0 auto" }}> <div className="container">
<div style={{ textAlign: "center", marginBottom: 48 }}> <div className="ln-section-head">
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>Dos servicios, un solo sistema</h2> <span className="ln-section-tag">Nuestros Servicios</span>
<p style={{ color: "#6B7280", fontSize: ".95rem" }}>Casillero personal para compras online y carga pesada para importadores B2B.</p> <h2 className="ln-section-title">Dos soluciones, un solo sistema</h2>
<p className="ln-section-sub">Casillero personal para compras online e importación B2B para mayoristas.</p>
</div> </div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 }}> <div className="ln-grid-2">
<div style={{ background: "#fff", borderRadius: 16, padding: 32, border: "2px solid #BFDBFE" }}> <div className="ln-service-card featured">
<div style={{ fontSize: "2.5rem", marginBottom: 12 }}>📦</div> <div className="ln-service-icon">📦</div>
<h3 style={{ fontWeight: 800, fontSize: "1.2rem", marginBottom: 8 }}>Casillero Personal</h3> <h3>Casillero Personal (B2C)</h3>
<p style={{ color: "#6B7280", marginBottom: 16 }}> <p>Para compras individuales en Amazon, eBay, Walmart y más. Suite personalizada, tracking en tiempo real y declaración SENAE automática bajo el régimen 4×4.</p>
Para compras individuales en Amazon, eBay, Walmart y más. Suite personalizada, tracking en tiempo real <ul className="ln-service-list">
y declaración SENAE automática bajo el régimen 4×4. {["Suite virtual asignada al registrarse", "Pre-alerta con carga de factura", "Calculadora de costos SENAE", "11 estados de tracking en tiempo real", "Notificaciones WhatsApp · SMS · Email", "Declaración aduanera incluida"].map(i => <li key={i}>{i}</li>)}
</p>
<ul style={{ color: "#374151", paddingLeft: 20, display: "flex", flexDirection: "column", gap: 6, fontSize: ".88rem" }}>
{["Suite virtual asignada al registrarse", "Pre-alerta con carga de factura", "Calculadora de costos SENAE", "11 estados de tracking en tiempo real", "Notificaciones WhatsApp · SMS · Email"].map(i => (
<li key={i}>{i}</li>
))}
</ul> </ul>
<Link href="/casillero" style={{ <Link href="/registro" className="btn btn-primary w-full" style={{ justifyContent: "center" }}>
display: "inline-block", marginTop: 20, background: "#0057FF", color: "#fff", Abrir casillero gratis
padding: "10px 20px", borderRadius: 8, fontWeight: 600, fontSize: ".88rem",
}}>
Conocer más
</Link> </Link>
</div> </div>
<div style={{ background: "#fff", borderRadius: 16, padding: 32, border: "2px solid #FED7AA" }}> <div className="ln-service-card">
<div style={{ fontSize: "2.5rem", marginBottom: 12 }}>🚢</div> <div className="ln-service-icon">🚢</div>
<h3 style={{ fontWeight: 800, fontSize: "1.2rem", marginBottom: 8 }}>Carga Pesada / Pallets B2B</h3> <h3>Carga Pesada / Pallets (B2B)</h3>
<p style={{ color: "#6B7280", marginBottom: 16 }}> <p>Para importadores mayoristas: pallets, contenedores y carga a granel. Cotización personalizada, certificación INEN y declaración formal DAI ante la SENAE.</p>
Para importadores mayoristas: pallets, contenedores, volumen alto. Cotización personalizada, <ul className="ln-service-list">
certificación INEN y declaración formal DAI ante la SENAE. {["Cotización especial por mercancía", "Verificación de requisitos INEN", "Declaración formal DAI", "Asesoría en partidas arancelarias", "Expediente B2B con seguimiento", "Agente dedicado en WhatsApp"].map(i => <li key={i}>{i}</li>)}
</p>
<ul style={{ color: "#374151", paddingLeft: 20, display: "flex", flexDirection: "column", gap: 6, fontSize: ".88rem" }}>
{["Cotización especial por tipo de mercancía", "Verificación de requisitos INEN", "Declaración formal DAI", "Asesoría en partidas arancelarias", "Expediente B2B con ID de seguimiento"].map(i => (
<li key={i}>{i}</li>
))}
</ul> </ul>
<Link href="/carga-pesada/cotizacion" style={{ <Link href="/carga-pesada/cotizacion" className="btn btn-accent w-full" style={{ justifyContent: "center" }}>
display: "inline-block", marginTop: 20, background: "#FF6B00", color: "#fff",
padding: "10px 20px", borderRadius: 8, fontWeight: 600, fontSize: ".88rem",
}}>
Solicitar cotización Solicitar cotización
</Link> </Link>
</div> </div>
@@ -198,28 +148,57 @@ function ServicesSection() {
); );
} }
function HowItWorksSection() {
const steps = [
{ n: "1", t: "Regístrate", d: "Crea tu cuenta y obtén tu Suite exclusiva en New Jersey al instante." },
{ n: "2", t: "Compra en EE.UU.", d: "Usa tu dirección Suite en Amazon, eBay, Walmart o cualquier tienda online." },
{ n: "3", t: "Recibimos en NJ", d: "Tu paquete llega a nuestra bodega. Lo verificamos, fotografiamos y preparamos." },
{ n: "4", t: "Te llega a Ecuador", d: "Enviamos con declaración SENAE incluida. Sigues todo con tu Tracking ID." },
];
return (
<section className="ln-section">
<div className="container">
<div className="ln-section-head">
<span className="ln-section-tag">Proceso</span>
<h2 className="ln-section-title">Así de simple. 4 pasos.</h2>
<p className="ln-section-sub">Desde tu compra hasta la puerta de tu casa en Ecuador, lo manejamos todo.</p>
</div>
<div className="ln-grid-4">
{steps.map(s => (
<div key={s.n} style={{ textAlign: "center", padding: "1.5rem 1rem" }}>
<div style={{
width: 64, height: 64, borderRadius: "50%",
background: "var(--white)", border: "3px solid var(--primary)",
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: "1.4rem", fontWeight: 800, color: "var(--primary)",
margin: "0 auto 1rem", boxShadow: "var(--shadow-md)",
}}>{s.n}</div>
<h4 style={{ fontWeight: 700, marginBottom: ".5rem" }}>{s.t}</h4>
<p style={{ fontSize: ".85rem", color: "var(--gray-500)" }}>{s.d}</p>
</div>
))}
</div>
</div>
</section>
);
}
function ModulesSection() { function ModulesSection() {
return ( return (
<section style={{ padding: "80px 40px", background: "#fff" }}> <section className="ln-section-gray">
<div style={{ maxWidth: 1100, margin: "0 auto" }}> <div className="container">
<div style={{ textAlign: "center", marginBottom: 48 }}> <div className="ln-section-head">
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>8 módulos integrados</h2> <span className="ln-section-tag">Plataforma</span>
<p style={{ color: "#6B7280" }}>Toda la operación logística cubierta en una sola plataforma.</p> <h2 className="ln-section-title">8 módulos integrados</h2>
<p className="ln-section-sub">Toda la operación logística cubierta en una sola plataforma.</p>
</div> </div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16 }}> <div className="ln-grid-4">
{MODULES.map((m) => ( {MODULES.map(m => (
<div key={m.title} style={{ <div key={m.title} className="ln-module-card">
background: "#F9FAFB", borderRadius: 12, padding: 20, <div className="ln-module-icon">{m.icon}</div>
border: "1px solid #E5E7EB", transition: "box-shadow .2s", <div className="ln-module-title">{m.title}</div>
}}> <p className="ln-module-desc">{m.desc}</p>
<div style={{ fontSize: "1.8rem", marginBottom: 10 }}>{m.icon}</div> <span className="ln-module-tag">{m.tag}</span>
<div style={{ fontWeight: 700, fontSize: ".9rem", marginBottom: 6 }}>{m.title}</div>
<p style={{ color: "#6B7280", fontSize: ".8rem", marginBottom: 10, lineHeight: 1.5 }}>{m.desc}</p>
<span style={{
background: "#EEF3FF", color: "#0057FF",
fontSize: ".68rem", fontWeight: 700, padding: "2px 8px",
borderRadius: 999,
}}>{m.tag}</span>
</div> </div>
))} ))}
</div> </div>
@@ -230,47 +209,40 @@ function ModulesSection() {
function TrackingSection() { function TrackingSection() {
return ( return (
<section style={{ padding: "80px 40px", background: "#F3F4F6" }}> <section className="ln-section">
<div style={{ maxWidth: 1100, margin: "0 auto" }}> <div className="container">
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 48, alignItems: "start" }}> <div className="ln-grid-2-1" style={{ gap: "3rem" }}>
<div> <div>
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 12 }}> <span className="ln-section-tag">Tracking en Tiempo Real</span>
Tracking en tiempo real <h2 className="ln-section-title" style={{ textAlign: "left", marginBottom: ".75rem" }}>
Sigue tu paquete en cada paso
</h2> </h2>
<p style={{ color: "#6B7280", marginBottom: 16 }}> <p style={{ color: "var(--gray-500)", marginBottom: "1rem", fontSize: ".95rem" }}>
Cada paquete recibe un ID único con 11 estados que cubren todo el ciclo de vida. Cada paquete recibe un ID único con 11 estados que cubren todo el ciclo de vida.
Búsqueda pública sin necesidad de crear cuenta. Búsqueda pública sin necesidad de crear cuenta.
</p> </p>
<div style={{ <div style={{
background: "#0D1117", borderRadius: 10, padding: "14px 18px", background: "var(--dark)", borderRadius: 10, padding: "14px 18px",
fontFamily: "monospace", fontSize: ".88rem", color: "#A5B4FC", fontFamily: "monospace", fontSize: ".88rem", color: "#A5B4FC",
marginBottom: 20, border: "1px solid rgba(0,87,255,.3)", marginBottom: "1.5rem", border: "1px solid rgba(0,87,255,.3)",
}}> }}>
Formato: <strong style={{ color: "#FF6B00" }}>EC-YYYYMMDD-XXXXXX</strong><br /> Formato: <strong style={{ color: "#FF6B00" }}>EC-YYYYMMDD-XXXXXX</strong><br />
Ejemplo: <strong>EC-20260506-082341</strong> Ejemplo: <strong>EC-20260628-082341</strong>
</div> </div>
<Link href="/tracking" style={{ <p style={{ fontSize: ".82rem", color: "var(--gray-500)", marginBottom: "1.5rem" }}>
background: "#0057FF", color: "#fff", Notificaciones automáticas por <strong>correo</strong>, <strong>SMS</strong> y <strong>WhatsApp</strong> en cada cambio de estado.
padding: "12px 24px", borderRadius: 8, fontWeight: 600, fontSize: ".9rem", </p>
display: "inline-block", <Link href="/tracking" className="btn btn-primary">
}}>
Buscar mi paquete Buscar mi paquete
</Link> </Link>
</div> </div>
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}> <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
{TRACKING_STATES.map((s) => ( {TRACKING_STATES.map(s => (
<div key={s.num} style={{ <div key={s.num} className="ln-tracking-state">
display: "flex", gap: 12, alignItems: "center", <div className="ln-state-dot" style={{ background: s.color }} />
background: "#fff", borderRadius: 8, padding: "8px 14px", <span className="ln-state-num">{s.num}</span>
border: "1px solid #E5E7EB", <span className="ln-state-name">{s.name}</span>
}}> <span className="ln-state-desc">{s.desc}</span>
<div style={{
width: 10, height: 10, borderRadius: "50%",
background: s.color, flexShrink: 0,
}} />
<span style={{ fontFamily: "monospace", fontSize: ".68rem", color: "#9CA3AF", minWidth: 22 }}>{s.num}</span>
<span style={{ fontFamily: "monospace", fontSize: ".78rem", fontWeight: 700, flex: 1 }}>{s.name}</span>
<span style={{ fontSize: ".75rem", color: "#6B7280" }}>{s.desc}</span>
</div> </div>
))} ))}
</div> </div>
@@ -282,46 +254,32 @@ function TrackingSection() {
function CalculatorPreviewSection() { function CalculatorPreviewSection() {
return ( return (
<section style={{ padding: "80px 40px", background: "#fff" }}> <section className="ln-section-gray">
<div style={{ maxWidth: 1100, margin: "0 auto", textAlign: "center" }}> <div className="container" style={{ textAlign: "center" }}>
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}> <span className="ln-section-tag">Calculadora SENAE</span>
Calculadora SENAE gratuita <h2 className="ln-section-title">Calcula tu costo antes de comprar</h2>
</h2> <p className="ln-section-sub" style={{ marginBottom: "2.5rem" }}>
<p style={{ color: "#6B7280", marginBottom: 40 }}> Sin registro. Ingresa peso y valor y obtén el desglose completo con impuestos SENAE.
Sin registro. Conoce el costo total de tu envío antes de comprar.
</p> </p>
<div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 16, marginBottom: 32 }}> <div className="ln-grid-4" style={{ marginBottom: "2rem" }}>
{SENAE_CATEGORIES.map((c) => ( {SENAE_CATEGORIES.map(c => (
<div key={c.name} style={{ <div key={c.name} className="ln-senae-card" style={{ background: c.bg, border: `1px solid ${c.color}30` }}>
borderRadius: 12, padding: 24, background: c.bg,
border: `1px solid ${c.color}30`, textAlign: "center",
}}>
<div style={{ fontWeight: 800, marginBottom: 4 }}>{c.name}</div> <div style={{ fontWeight: 800, marginBottom: 4 }}>{c.name}</div>
<div style={{ fontSize: "2.2rem", fontWeight: 900, color: c.color, lineHeight: 1, marginBottom: 8 }}>{c.rate}</div> <div className="ln-senae-rate" style={{ color: c.color }}>{c.rate}</div>
<div style={{ fontSize: ".75rem", color: "#4B5563" }}>{c.desc}</div> <div style={{ fontSize: ".75rem", color: "#4B5563" }}>{c.desc}</div>
</div> </div>
))} ))}
</div> </div>
<div style={{ <div className="ln-calc-formula">
background: "#0D1117", borderRadius: 12, padding: "20px 24px", <span style={{ color: "rgba(255,255,255,.25)" }}>{"// Fórmula (doc §15)"}</span><br />
fontFamily: "monospace", fontSize: ".82rem", color: "#A5B4FC",
textAlign: "left", maxWidth: 600, margin: "0 auto 28px",
lineHeight: 2, border: "1px solid rgba(255,255,255,.08)",
}}>
<span style={{ color: "rgba(255,255,255,.25)" }}>// Fórmula (doc §15)</span><br />
<span style={{ color: "#6EE7B7", fontWeight: 700 }}>Flete</span> = Peso_final × <span style={{ color: "#FCD34D" }}>$3.50</span>/lb<br /> <span style={{ color: "#6EE7B7", fontWeight: 700 }}>Flete</span> = Peso_final × <span style={{ color: "#FCD34D" }}>$3.50</span>/lb<br />
<span style={{ color: "#6EE7B7", fontWeight: 700 }}>Seguro</span> = Valor × <span style={{ color: "#FCD34D" }}>2%</span><br /> <span style={{ color: "#6EE7B7", fontWeight: 700 }}>Seguro</span> = Valor × <span style={{ color: "#FCD34D" }}>2%</span><br />
<span style={{ color: "#6EE7B7", fontWeight: 700 }}>FODINFA</span> = Valor × <span style={{ color: "#FCD34D" }}>0.5%</span><br /> <span style={{ color: "#6EE7B7", fontWeight: 700 }}>FODINFA</span> = Valor × <span style={{ color: "#FCD34D" }}>0.5%</span><br />
<span style={{ color: "#6EE7B7", fontWeight: 700 }}>Arancel</span> = Valor × <span style={{ color: "#FCD34D" }}>% categoría</span><br /> <span style={{ color: "#6EE7B7", fontWeight: 700 }}>Arancel</span> = Valor × <span style={{ color: "#FCD34D" }}>% categoría</span><br />
<span style={{ color: "#6EE7B7", fontWeight: 700 }}>IVA</span> = (Valor + FODINFA + Arancel) × <span style={{ color: "#FCD34D" }}>15%</span><br />
<span style={{ color: "#F9A8D4", fontWeight: 700 }}>TOTAL</span> = Flete + Seguro + FODINFA + Arancel + IVA <span style={{ color: "#F9A8D4", fontWeight: 700 }}>TOTAL</span> = Flete + Seguro + FODINFA + Arancel + IVA
</div> </div>
<Link href="/calculadora" style={{ <Link href="/calculadora" className="btn btn-primary btn-lg">
background: "#0057FF", color: "#fff", Ir a la calculadora
padding: "14px 32px", borderRadius: 8, fontWeight: 700,
display: "inline-block", fontSize: ".95rem",
}}>
Calcular mi envío
</Link> </Link>
</div> </div>
</section> </section>
@@ -330,25 +288,20 @@ function CalculatorPreviewSection() {
function RolesSection() { function RolesSection() {
return ( return (
<section style={{ padding: "80px 40px", background: "#F3F4F6" }}> <section className="ln-section">
<div style={{ maxWidth: 1100, margin: "0 auto" }}> <div className="container">
<div style={{ textAlign: "center", marginBottom: 48 }}> <div className="ln-section-head">
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>6 roles, accesos diferenciados</h2> <span className="ln-section-tag">Accesos</span>
<p style={{ color: "#6B7280" }}>Cada usuario tiene exactamente lo que necesita. Ni más, ni menos.</p> <h2 className="ln-section-title">6 roles, accesos diferenciados</h2>
<p className="ln-section-sub">Cada usuario tiene exactamente lo que necesita. Ni más, ni menos.</p>
</div> </div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 16 }}> <div className="ln-grid-3">
{ROLES.map((r) => ( {ROLES.map(r => (
<div key={r.name} style={{ <div key={r.name} className="ln-role-card" style={{ background: r.bg }}>
background: r.bg, borderRadius: 12, padding: 20, <div className="ln-role-icon">{r.icon}</div>
border: "1px solid #E5E7EB", <div className="ln-role-name">{r.name}</div>
}}> <p className="ln-role-desc">{r.desc}</p>
<div style={{ fontSize: "1.5rem", marginBottom: 10 }}>{r.icon}</div> <span className="ln-role-badge">{r.badge}</span>
<div style={{ fontWeight: 700, marginBottom: 6 }}>{r.name}</div>
<p style={{ color: "#6B7280", fontSize: ".82rem", marginBottom: 12, lineHeight: 1.5 }}>{r.desc}</p>
<span style={{
background: "rgba(0,0,0,.06)", fontSize: ".7rem", fontWeight: 700,
padding: "2px 10px", borderRadius: 999,
}}>{r.badge}</span>
</div> </div>
))} ))}
</div> </div>
@@ -359,37 +312,34 @@ function RolesSection() {
function CompaniesSection() { function CompaniesSection() {
return ( return (
<section style={{ padding: "80px 40px", background: "#fff" }}> <section className="ln-section-gray">
<div style={{ maxWidth: 1100, margin: "0 auto", textAlign: "center" }}> <div className="container">
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>Operación legal en dos países</h2> <div className="ln-section-head">
<p style={{ color: "#6B7280", marginBottom: 40 }}>Empresas constituidas legalmente en Ecuador y EE.UU.</p> <span className="ln-section-tag">Confianza y Legalidad</span>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, maxWidth: 800, margin: "0 auto" }}> <h2 className="ln-section-title">Operación legal en dos países</h2>
<p className="ln-section-sub">Empresas constituidas legalmente en Ecuador y EE.UU. con representación visible en ambos países.</p>
</div>
<div className="ln-grid-2" style={{ maxWidth: 800, margin: "0 auto" }}>
{[ {[
{ {
flag: "🇪🇨", flag: "🇪🇨",
name: "Moraworld Imports S.A.S.", name: "Moraworld Imports S.A.S.",
detail: "Constituida en Ecuador. Representante legal en Cuenca. RUC activo, permisos aduaneros vigentes ante la SENAE.", desc: "Constituida en Ecuador. Representante legal en Cuenca. RUC activo, permisos aduaneros vigentes ante la SENAE.",
badge: "Ecuador · S.A.S. · SENAE · Cuenca", badge: "Ecuador · S.A.S. · SENAE · Cuenca",
}, },
{ {
flag: "🇺🇸", flag: "🇺🇸",
name: "Mora Global Import LLC", name: "Mora Global Import LLC",
detail: "LLC registrada en New Jersey, EE.UU. Bodega operativa en 150 N Day St, City of Orange, NJ 07050.", desc: "LLC registrada en New Jersey, EE.UU. Bodega operativa en 150 N Day St, City of Orange, NJ 07050.",
badge: "New Jersey · LLC · NJ 07050", badge: "New Jersey · LLC · NJ 07050",
}, },
].map((c) => ( ].map(c => (
<div key={c.name} style={{ <div key={c.name} className="ln-company-card">
background: "#fff", borderRadius: 16, padding: 28, <span className="ln-company-flag">{c.flag}</span>
border: "2px solid #E5E7EB", display: "flex", gap: 16, alignItems: "flex-start", <div>
}}> <div className="ln-company-name">{c.name}</div>
<span style={{ fontSize: "2.5rem" }}>{c.flag}</span> <p className="ln-company-desc">{c.desc}</p>
<div style={{ textAlign: "left" }}> <span className="ln-company-badge">{c.badge}</span>
<div style={{ fontWeight: 800, marginBottom: 6 }}>{c.name}</div>
<p style={{ color: "#6B7280", fontSize: ".82rem", lineHeight: 1.6, marginBottom: 10 }}>{c.detail}</p>
<span style={{
background: "#EEF3FF", color: "#0057FF",
fontSize: ".7rem", fontWeight: 700, padding: "2px 10px", borderRadius: 999,
}}>{c.badge}</span>
</div> </div>
</div> </div>
))} ))}
@@ -399,61 +349,76 @@ function CompaniesSection() {
); );
} }
function CtaSection() {
return (
<section className="ln-cta-section">
<div className="container">
<h2>¿Listo para comprar en EE.UU.<br />y recibir en Ecuador?</h2>
<p>Regístrate gratis y obtén tu Suite en New Jersey en menos de 2 minutos.</p>
<Link href="/registro" style={{
display: "inline-block", background: "var(--white)", color: "var(--primary)",
padding: "16px 36px", borderRadius: 8, fontWeight: 700, fontSize: "1rem",
}}>
Abrir mi casillero gratis
</Link>
</div>
</section>
);
}
function Footer() { function Footer() {
return ( return (
<footer style={{ <footer className="ln-footer">
background: "#0D1117", padding: "48px 40px", color: "rgba(255,255,255,.5)", <div className="container" style={{ textAlign: "center" }}>
textAlign: "center", <div className="ln-footer-logo">
}}> Moraworld<span>.</span>Imports
<div style={{ fontSize: "1.1rem", fontWeight: 800, color: "#fff", marginBottom: 8 }}> </div>
Moraworld<span style={{ color: "#FF6B00" }}>.</span>Imports <p style={{ fontSize: ".82rem", marginBottom: ".5rem" }}>
</div> Moraworld Imports S.A.S. · Mora Global Import LLC
<p style={{ fontSize: ".82rem", marginBottom: 20 }}> </p>
Moraworld Imports S.A.S. · Mora Global Import LLC <div className="ln-footer-badges">
</p> {["ISO/IEC 27001", "SENAE Autorizado", "INEN", "LOPDP Ecuador"].map(b => (
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "center", marginBottom: 24 }}> <span key={b} className="ln-footer-badge">{b}</span>
{["ISO/IEC 27001", "SENAE Autorizado", "INEN", "LOPDP Ecuador", "Multi-Tenant"].map((b) => ( ))}
<span key={b} style={{ </div>
background: "rgba(255,255,255,.07)", color: "rgba(255,255,255,.5)", <nav className="ln-footer-links">
fontSize: ".7rem", fontWeight: 700, padding: "4px 12px", borderRadius: 999, <Link href="/como-funciona">Cómo funciona</Link>
}}>{b}</span> <Link href="/calculadora">Calculadora</Link>
))} <Link href="/tracking">Tracking</Link>
</div> <Link href="/tarifas">Tarifas</Link>
<div style={{ display: "flex", gap: 24, justifyContent: "center", flexWrap: "wrap", fontSize: ".82rem" }}> <Link href="/quienes-somos">Quiénes somos</Link>
<Link href="/como-funciona">Cómo funciona</Link> <Link href="/casillero">Casillero</Link>
<Link href="/calculadora">Calculadora</Link> <Link href="/carga-pesada">Carga Pesada</Link>
<Link href="/tracking">Tracking</Link> <Link href="/login">Iniciar sesión</Link>
<Link href="/tarifas">Tarifas</Link> </nav>
<Link href="/quienes-somos">Quiénes somos</Link> <div className="ln-footer-legal">
<Link href="/casillero">Casillero</Link> 🛡 Los datos personales de clientes ecuatorianos están protegidos por la{" "}
<Link href="/carga-pesada">Carga Pesada</Link> <strong style={{ color: "rgba(255,255,255,.5)" }}>Ley Orgánica de Protección de Datos Personales (LOPDP) de Ecuador</strong>.
</div> Al procesarse en EE.UU., aplica también la normativa del estado de New Jersey.
{/* LOPDP §21 — aviso de protección de datos */} <br />
<div style={{ borderTop: "1px solid rgba(255,255,255,.07)", marginTop: 24, paddingTop: 16, fontSize: ".72rem", color: "rgba(255,255,255,.3)", lineHeight: 1.7, textAlign: "center" }}> © 2026 Moraworld Imports S.A.S. (Ecuador) · Mora Global Import LLC (NJ, EE.UU.)
🛡 Los datos personales de clientes ecuatorianos están protegidos por la{" "} </div>
<strong style={{ color: "rgba(255,255,255,.5)" }}>Ley Orgánica de Protección de Datos Personales (LOPDP) de Ecuador</strong>.
Al procesarse en EE.UU., aplica también la normativa del estado de New Jersey.
Moraworld Imports S.A.S. (Ecuador) · Mora Global Import LLC (NJ, EE.UU.) · Documento v1.1 2026
</div> </div>
</footer> </footer>
); );
} }
// ─── Página principal ───────────────────────────────────────── // ─── Página principal ────────────────────────────────────
export default function HomePage() { export default function HomePage() {
return ( return (
<> <>
<Navbar /> <LandingNavbar />
<Hero /> <Hero />
<ServicesSection /> <ServicesSection />
<HowItWorksSection />
<ModulesSection /> <ModulesSection />
<TrackingSection /> <TrackingSection />
<CalculatorPreviewSection /> <CalculatorPreviewSection />
<RolesSection /> <RolesSection />
<CompaniesSection /> <CompaniesSection />
<CtaSection />
<Footer /> <Footer />
<WhatsAppFloat />
</> </>
); );
} }
+11 -2
View File
@@ -1,5 +1,9 @@
# Coolify Deployment — docker-compose for production (managed DB/Redis by Coolify) # Coolify Deployment — docker-compose for production (managed DB/Redis by Coolify)
networks:
coolify:
external: true
services: services:
api: api:
build: build:
@@ -7,6 +11,8 @@ services:
dockerfile: apps/api/Dockerfile dockerfile: apps/api/Dockerfile
container_name: moraworld-api container_name: moraworld-api
restart: unless-stopped restart: unless-stopped
networks:
- coolify
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- PORT=3001 - PORT=3001
@@ -24,7 +30,7 @@ services:
- S3_PUBLIC_URL=${S3_PUBLIC_URL} - S3_PUBLIC_URL=${S3_PUBLIC_URL}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"] test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3001/api/health"]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3
@@ -49,13 +55,16 @@ services:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
container_name: moraworld-web container_name: moraworld-web
restart: unless-stopped restart: unless-stopped
networks:
- coolify
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- PORT=3000 - PORT=3000
- HOSTNAME=0.0.0.0
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
- NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL} - NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/status"] test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/status"]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3
+78 -24
View File
@@ -5,6 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Moraworld Imports — Tu casillero en New Jersey. Compra en Amazon, eBay y más. Enviamos a Ecuador con declaración aduanera incluida." /> <meta name="description" content="Moraworld Imports — Tu casillero en New Jersey. Compra en Amazon, eBay y más. Enviamos a Ecuador con declaración aduanera incluida." />
<title>Moraworld Imports — Casillero en EE.UU. para Ecuador</title> <title>Moraworld Imports — Casillero en EE.UU. para Ecuador</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>" />
<meta property="og:title" content="Moraworld Imports — Casillero en EE.UU. para Ecuador" />
<meta property="og:description" content="Tu casillero en New Jersey. Compra en Amazon, eBay y más. Enviamos a Ecuador con declaración SENAE incluida." />
<meta property="og:type" content="website" />
<meta name="theme-color" content="#0057FF" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
@@ -470,7 +475,7 @@
.footer-bottom { .footer-bottom {
border-top: 1px solid rgba(255,255,255,.06); border-top: 1px solid rgba(255,255,255,.06);
padding-top: 28px; display: flex; justify-content: space-between; padding-top: 28px; display: flex; justify-content: space-between;
align-items: center; flex-wrap: gap; align-items: center; flex-wrap: wrap; gap: 16px;
} }
.footer-bottom p { font-size: .82rem; } .footer-bottom p { font-size: .82rem; }
.footer-badges { display: flex; gap: 12px; } .footer-badges { display: flex; gap: 12px; }
@@ -480,6 +485,24 @@
border-radius: 999px; border-radius: 999px;
} }
/* ── MOBILE MENU ── */
.mobile-menu {
display: none;
position: fixed; top: 64px; left: 0; right: 0; z-index: 99;
background: var(--white); border-bottom: 1px solid var(--gray-200);
padding: 16px 24px 24px; box-shadow: 0 8px 32px rgba(0,0,0,.12);
}
.mobile-menu.open { display: block; }
.mobile-nav-links { display: flex; flex-direction: column; }
.mobile-nav-links a { padding: 13px 0; border-bottom: 1px solid var(--gray-100); font-size: .95rem; font-weight: 500; color: var(--gray-700); display: block; }
.mobile-nav-links a:last-child { border-bottom: none; }
.mobile-nav-links a:hover { color: var(--primary); }
.mobile-nav-cta { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.mobile-nav-cta .btn { justify-content: center; width: 100%; }
.hamburger.active span:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
.hamburger.active span:nth-child(2) { opacity: 0; }
.hamburger.active span:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }
/* ── WHATSAPP FLOAT ── */ /* ── WHATSAPP FLOAT ── */
.wa-float { .wa-float {
position: fixed; bottom: 28px; right: 28px; z-index: 999; position: fixed; bottom: 28px; right: 28px; z-index: 999;
@@ -543,7 +566,7 @@
<a href="#como-funciona">Cómo funciona</a> <a href="#como-funciona">Cómo funciona</a>
<a href="#calculadora">Calculadora</a> <a href="#calculadora">Calculadora</a>
<a href="#tracking">Tracking</a> <a href="#tracking">Tracking</a>
<a href="#carga-pesada">Carga Pesada</a> <a href="#cotizacion">Carga Pesada</a>
<a href="#nosotros">Nosotros</a> <a href="#nosotros">Nosotros</a>
</div> </div>
<div class="nav-cta"> <div class="nav-cta">
@@ -556,6 +579,23 @@
</div> </div>
</nav> </nav>
<!-- ─────────────────── MOBILE MENU ─────────────────── -->
<div class="mobile-menu" id="mobile-menu">
<div class="mobile-nav-links">
<a href="#servicios" onclick="closeMobileMenu()">Servicios</a>
<a href="#como-funciona" onclick="closeMobileMenu()">Cómo funciona</a>
<a href="#calculadora" onclick="closeMobileMenu()">Calculadora</a>
<a href="#tracking" onclick="closeMobileMenu()">Tracking</a>
<a href="#cotizacion" onclick="closeMobileMenu()">Carga Pesada</a>
<a href="#nosotros" onclick="closeMobileMenu()">Nosotros</a>
<a href="#contacto" onclick="closeMobileMenu()">Contacto</a>
</div>
<div class="mobile-nav-cta">
<a href="login.html" class="btn btn-outline">Iniciar sesión</a>
<a href="registro.html" class="btn btn-primary">Abrir casillero gratis →</a>
</div>
</div>
<!-- ─────────────────── HERO ─────────────────── --> <!-- ─────────────────── HERO ─────────────────── -->
<section class="hero"> <section class="hero">
<div class="container"> <div class="container">
@@ -572,8 +612,8 @@
</div> </div>
<div class="hero-stats"> <div class="hero-stats">
<div class="stat"> <div class="stat">
<div class="stat-num">150 N</div> <div class="stat-num">$3.50</div>
<div class="stat-label">Day St, Orange NJ</div> <div class="stat-label">por libra (flete)</div>
</div> </div>
<div class="stat"> <div class="stat">
<div class="stat-num">24/7</div> <div class="stat-num">24/7</div>
@@ -982,7 +1022,7 @@
<input type="file" id="factura-upload" style="display:none;" accept=".pdf,image/*" /> <input type="file" id="factura-upload" style="display:none;" accept=".pdf,image/*" />
</div> </div>
</div> </div>
<button class="calc-btn">Registrar pre-alerta →</button> <button class="calc-btn" onclick="submitPrealertaLanding()">Registrar pre-alerta →</button>
</div> </div>
</div> </div>
</div> </div>
@@ -1252,7 +1292,7 @@
<h3>Moraworld<span>.</span>Imports</h3> <h3>Moraworld<span>.</span>Imports</h3>
<p>Conectamos el mercado americano con los consumidores ecuatorianos. Bodega en New Jersey, representación legal en Cuenca, Ecuador.</p> <p>Conectamos el mercado americano con los consumidores ecuatorianos. Bodega en New Jersey, representación legal en Cuenca, Ecuador.</p>
<p style="margin-top:12px; font-size:.8rem;"> <p style="margin-top:12px; font-size:.8rem;">
📍 150 N Day St, Suite EC-, City of Orange, NJ 07050<br/> 📍 150 N Day St, City of Orange, NJ 07050<br/>
📍 Cuenca, Ecuador 📍 Cuenca, Ecuador
</p> </p>
</div> </div>
@@ -1260,7 +1300,7 @@
<h4>Servicios</h4> <h4>Servicios</h4>
<ul> <ul>
<li><a href="#servicios">Casillero Personal</a></li> <li><a href="#servicios">Casillero Personal</a></li>
<li><a href="#carga-pesada">Carga Pesada</a></li> <li><a href="#cotizacion">Carga Pesada</a></li>
<li><a href="#calculadora">Calculadora</a></li> <li><a href="#calculadora">Calculadora</a></li>
<li><a href="#tracking">Tracking</a></li> <li><a href="#tracking">Tracking</a></li>
<li><a href="#prealerta">Pre-alerta</a></li> <li><a href="#prealerta">Pre-alerta</a></li>
@@ -1324,9 +1364,26 @@
function toggleWa(){ function toggleWa(){
document.getElementById('wa-float').classList.toggle('open'); document.getElementById('wa-float').classList.toggle('open');
} }
/* Mobile menu */
function closeMobileMenu(){
document.getElementById('mobile-menu').classList.remove('open');
document.getElementById('hamburger').classList.remove('active');
}
document.getElementById('hamburger').addEventListener('click', function(){
this.classList.toggle('active');
document.getElementById('mobile-menu').classList.toggle('open');
});
/* Close menus when clicking outside */
document.addEventListener('click', function(e){ document.addEventListener('click', function(e){
const wf = document.getElementById('wa-float'); const wf = document.getElementById('wa-float');
if(!wf.contains(e.target)) wf.classList.remove('open'); if(!wf.contains(e.target)) wf.classList.remove('open');
const menu = document.getElementById('mobile-menu');
const hamburger = document.getElementById('hamburger');
if(menu.classList.contains('open') && !menu.contains(e.target) && !hamburger.contains(e.target)){
closeMobileMenu();
}
}); });
/* FAQ accordion */ /* FAQ accordion */
@@ -1346,15 +1403,11 @@
const valor = parseFloat(document.getElementById('c-value').value) || 0; const valor = parseFloat(document.getElementById('c-value').value) || 0;
const cat = document.getElementById('c-cat').value; const cat = document.getElementById('c-cat').value;
// Peso volumétrico (cm³ / 139 = lbs volumétricas aprox)
const volWeight = (largo * ancho * alto) / 139; const volWeight = (largo * ancho * alto) / 139;
const pesoFinal = Math.max(weight, volWeight); const pesoFinal = Math.max(weight, volWeight);
// Tarifas arancel
const aranceles = { '4x4': 0, 'B': 0.10, 'C': 0.20, 'D': 0.10 }; const aranceles = { '4x4': 0, 'B': 0.10, 'C': 0.20, 'D': 0.10 };
const tarifaFlete = 3.50; // USD / lb const flete = pesoFinal * 3.50;
const flete = pesoFinal * tarifaFlete;
const seguro = valor * 0.02; const seguro = valor * 0.02;
const fodinfa = valor * 0.005; const fodinfa = valor * 0.005;
const arancel = valor * (aranceles[cat] || 0); const arancel = valor * (aranceles[cat] || 0);
@@ -1363,13 +1416,13 @@
const fmt = n => '$' + n.toFixed(2); const fmt = n => '$' + n.toFixed(2);
document.getElementById('r-peso').textContent = pesoFinal.toFixed(2) + ' lbs'; document.getElementById('r-peso').textContent = pesoFinal.toFixed(2) + ' lbs';
document.getElementById('r-flete').textContent = fmt(flete); document.getElementById('r-flete').textContent = fmt(flete);
document.getElementById('r-seguro').textContent = fmt(seguro); document.getElementById('r-seguro').textContent = fmt(seguro);
document.getElementById('r-fodinfa').textContent= fmt(fodinfa); document.getElementById('r-fodinfa').textContent = fmt(fodinfa);
document.getElementById('r-arancel').textContent= fmt(arancel) + (cat === '4x4' ? ' (exento)' : ''); document.getElementById('r-arancel').textContent = fmt(arancel) + (cat === '4x4' ? ' (exento)' : '');
document.getElementById('r-iva').textContent = fmt(iva); document.getElementById('r-iva').textContent = fmt(iva);
document.getElementById('r-total').textContent = fmt(total); document.getElementById('r-total').textContent = fmt(total);
} }
/* Tracking demo */ /* Tracking demo */
@@ -1380,11 +1433,12 @@
document.getElementById('tracking-result').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); document.getElementById('tracking-result').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} }
/* Hamburger menu (mobile) */ /* Pre-alerta landing */
document.getElementById('hamburger').addEventListener('click', function(){ function submitPrealertaLanding(){
// Simple toggle — en producción se expandiría el menú móvil const tracking = document.querySelector('.prealerta-form input[type=text]').value.trim();
this.classList.toggle('active'); if(!tracking){ alert('Ingresa el número de tracking del vendedor.'); return; }
}); alert('✅ Pre-alerta registrada. El equipo de bodega en NJ estará atento a la llegada de tu paquete. ¡Gracias!');
}
</script> </script>
</body> </body>
+2
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Iniciar sesión — Moraworld Imports</title> <title>Iniciar sesión — Moraworld Imports</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>" />
<meta name="theme-color" content="#0057FF" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<style> <style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+2
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Mi Casillero — Moraworld Imports</title> <title>Mi Casillero — Moraworld Imports</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>" />
<meta name="theme-color" content="#0057FF" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet"/> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet"/>
<style> <style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+2
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Crear cuenta — Moraworld Imports</title> <title>Crear cuenta — Moraworld Imports</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>" />
<meta name="theme-color" content="#0057FF" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<style> <style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}