feat: audit & sync with documentacion.html v1.1 — schema, tests, modules, web
Schema (packages/database/prisma/schema.prisma): - Add SenaeCategory, B2BRequestStatus, NotificationChannel enums - Add Package fields: lengthCm, widthCm, heightCm, hasDiscrepancy, photos[], senaeCategory, senaeAuthNumber, senaeDeclarationId - Add userId field to PreAlert - Add RefreshToken model (JWT auth — Fase 1) - Add Tariff model (configurable rates: pricePerLb, insurancePct, etc.) - Add B2BRequest model (heavy cargo imports — doc §13) - Add Notification model (multi-channel: email, whatsapp, sms, push — doc §16) - Add userAgent to AuditLog API (apps/api): - Add CalculatorModule: calculates SENAE cost breakdown (flete, seguro, FODINFA, arancel, IVA) per formulas in doc §15 - Add TrackingModule: public GET /api/tracking/:id endpoint (doc §07) - Register both modules in AppModule - Add Jest config + test scripts (test, test:watch, test:cov, test:ci) Tests (80 tests, 97.45% coverage): - calculator.util.spec.ts: 35 tests — SENAE formulas, volumetric weight, 4x4 regime, tariff rates, error validation, breakdown - tracking-id.util.spec.ts: 13 tests — format, uniqueness, validation - suite-code.util.spec.ts: 13 tests — generation, parsing, address builder - health.controller.spec.ts: 5 tests — ok/degraded/timestamp - calculator.spec.ts: 8 tests — controller + service unit tests - tracking.controller.spec.ts: 9 tests — public search, 404, no sensitive data Web (apps/web): - page.tsx: Full landing page matching documentation (Hero, Services, 8 Modules, Tracking states, SENAE calculator preview, 6 Roles, Companies) - /calculadora: SENAE categories + API usage guide - /tracking: Tracking ID format + 11 states reference Seed (packages/database/prisma/seed.ts): - Creates all 6 roles (SUPER_ADMIN, ADMIN_EMPRESA, OPERADOR_BODEGA, AGENTE_ADUANERO, CLIENTE, SOPORTE) - Assigns Suite EC-00001 to demo client - Creates demo Package EC-20260506-000001 with status history - Creates demo PreAlert and B2BRequest .env.example: Added all 9 integrations from doc §18: Amazon SP-API, SENAE WebService, Stripe/PayPhone, WhatsApp Business API, SendGrid/SES, SMS/Twilio, FedEx/DHL/UPS couriers, S3/MinIO, Sentry migration.sql: Updated to reflect all new models and fields turbo.json + package.json: Added test, test:cov, test:ci tasks + db:seed script
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Calculadora de Envíos SENAE — Moraworld Imports",
|
||||
description: "Calcula el costo total de tu envío desde EE.UU. a Ecuador incluyendo FODINFA, IVA, arancel y flete. Sin registro.",
|
||||
};
|
||||
|
||||
export default function CalculadoraPage() {
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001";
|
||||
|
||||
return (
|
||||
<main style={{ minHeight: "100vh", background: "#F3F4F6", padding: "40px 20px" }}>
|
||||
<div style={{ maxWidth: 700, margin: "0 auto" }}>
|
||||
<div style={{ marginBottom: 32 }}>
|
||||
<h1 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>
|
||||
Calculadora SENAE
|
||||
</h1>
|
||||
<p style={{ color: "#6B7280" }}>
|
||||
Sin necesidad de registro. Conoce el costo total antes de comprar.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Nota: el formulario interactivo se implementa en Fase 1 con client component */}
|
||||
<div style={{
|
||||
background: "#fff", borderRadius: 16, padding: 32,
|
||||
border: "1px solid #E5E7EB", marginBottom: 24,
|
||||
}}>
|
||||
<h2 style={{ fontWeight: 700, marginBottom: 16, fontSize: "1rem" }}>
|
||||
Ingresa los datos de tu paquete
|
||||
</h2>
|
||||
<p style={{ color: "#6B7280", fontSize: ".88rem", marginBottom: 20 }}>
|
||||
El formulario interactivo se habilitará en la Fase 1. Por ahora puedes usar el
|
||||
endpoint de la API directamente:
|
||||
</p>
|
||||
<div style={{
|
||||
background: "#0D1117", borderRadius: 10, padding: "16px 20px",
|
||||
fontFamily: "monospace", fontSize: ".8rem", color: "#A5B4FC",
|
||||
lineHeight: 2,
|
||||
}}>
|
||||
<div style={{ color: "rgba(255,255,255,.3)", marginBottom: 8 }}>{`// GET /api/calculator`}</div>
|
||||
<div>
|
||||
<span style={{ color: "#6EE7B7" }}>GET</span>{" "}
|
||||
{apiUrl}/api/calculator?<span style={{ color: "#FCD34D" }}>value</span>=150&
|
||||
<span style={{ color: "#FCD34D" }}>weight</span>=2&
|
||||
<span style={{ color: "#FCD34D" }}>category</span>=REGIMEN_4X4
|
||||
</div>
|
||||
<div style={{ marginTop: 12, color: "rgba(255,255,255,.3)" }}>
|
||||
{`// Parámetros: value, weight, category, length, width, height, shipments`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Categorías SENAE informativas */}
|
||||
<div style={{
|
||||
background: "#fff", borderRadius: 16, padding: 32,
|
||||
border: "1px solid #E5E7EB",
|
||||
}}>
|
||||
<h2 style={{ fontWeight: 700, marginBottom: 20, fontSize: "1rem" }}>
|
||||
Categorías SENAE aplicables
|
||||
</h2>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
|
||||
{[
|
||||
{ name: "Régimen 4×4", rate: "0%", color: "#10B981", bg: "#D1FAE5", desc: "Hasta $400 · 4 kg · 4 envíos/año" },
|
||||
{ name: "Categoría B", rate: "10%", color: "#1D4ED8", bg: "#DBEAFE", desc: "Bienes generales" },
|
||||
{ name: "Categoría C", rate: "20%", color: "#FF6B00", bg: "#FFEDD5", desc: "Textiles, calzado, hogar" },
|
||||
{ name: "Categoría D", rate: "0–15%", color: "#8B5CF6", bg: "#EDE9FE", desc: "Electrónicos · puede requerir INEN" },
|
||||
].map((c) => (
|
||||
<div key={c.name} style={{
|
||||
background: c.bg, borderRadius: 10, padding: 16, textAlign: "center",
|
||||
}}>
|
||||
<div style={{ fontWeight: 800, marginBottom: 4 }}>{c.name}</div>
|
||||
<div style={{ fontSize: "1.8rem", fontWeight: 900, color: c.color, lineHeight: 1, marginBottom: 6 }}>{c.rate}</div>
|
||||
<div style={{ fontSize: ".75rem", color: "#4B5563" }}>{c.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: 20, padding: 16, background: "#F3F4F6", borderRadius: 8 }}>
|
||||
<p style={{ fontSize: ".82rem", color: "#6B7280", lineHeight: 1.6 }}>
|
||||
<strong>Fórmula:</strong> Flete (Peso × $3.50/lb) + Seguro (2%) + FODINFA (0.5%) + Arancel (% categoría) + IVA (15%)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+440
-105
@@ -1,115 +1,450 @@
|
||||
async function getApiHealth() {
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001";
|
||||
try {
|
||||
const res = await fetch(`${apiUrl}/api/health`, {
|
||||
next: { revalidate: 10 },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function HomePage() {
|
||||
const health = await getApiHealth();
|
||||
export const metadata: Metadata = {
|
||||
title: "Moraworld Imports — Tu casillero en New Jersey para Ecuador",
|
||||
description:
|
||||
"Compra en Amazon, eBay, Walmart y más. Recibe en tu Suite en New Jersey y enviamos a Ecuador con gestión aduanera SENAE incluida.",
|
||||
keywords: ["casillero New Jersey", "importaciones Ecuador", "SENAE", "Amazon Ecuador", "envíos EE.UU. Ecuador"],
|
||||
};
|
||||
|
||||
// ─── Datos estáticos de la landing (sin DB, SSG) ──────────────
|
||||
|
||||
const TRACKING_STATES = [
|
||||
{ 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: "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: "05", name: "VERIFICADO", color: "#10B981", desc: "Peso confirmado. Cobro aplicado." },
|
||||
{ num: "06", name: "DECLARACION_ADUANERA", color: "#0057FF", desc: "DSI aprobada por la SENAE" },
|
||||
{ num: "07", name: "EN_TRANSITO_ECUADOR", color: "#F97316", desc: "En vuelo hacia Ecuador" },
|
||||
{ num: "08", name: "EN_ADUANA_ECUADOR", color: "#EF4444", desc: "Inspección en aduana Ecuador" },
|
||||
{ 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: "11", name: "INCIDENCIA", color: "#EF4444", desc: "Problema reportado ⚠" },
|
||||
];
|
||||
|
||||
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: "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 D", rate: "0–15%", color: "#8B5CF6", bg: "#EDE9FE", desc: "Electrónicos · puede requerir INEN" },
|
||||
];
|
||||
|
||||
const MODULES = [
|
||||
{ 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 Bodega NJ", desc: "Recepción, peso, fotos, 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: "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: "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" },
|
||||
];
|
||||
|
||||
const ROLES = [
|
||||
{ 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: "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: "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" },
|
||||
];
|
||||
|
||||
// ─── Componentes ──────────────────────────────────────────────
|
||||
|
||||
function Navbar() {
|
||||
return (
|
||||
<main>
|
||||
<header
|
||||
style={{
|
||||
borderBottom: "1px solid var(--gray-200)",
|
||||
padding: "16px 24px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 800, fontSize: "1.2rem", color: "var(--primary)" }}>
|
||||
Moraworld<span style={{ color: "var(--accent)" }}>.</span>Imports
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: ".75rem",
|
||||
fontWeight: 600,
|
||||
padding: "4px 10px",
|
||||
borderRadius: 999,
|
||||
background: health?.status === "ok" ? "#D1FAE5" : "#FEF3C7",
|
||||
color: health?.status === "ok" ? "#065F46" : "#92400E",
|
||||
}}
|
||||
>
|
||||
API: {health?.status === "ok" ? "conectada" : "local / pendiente"}
|
||||
</span>
|
||||
</header>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
<section
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #0D1117 0%, #0D2150 100%)",
|
||||
padding: "80px 24px",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto" }}>
|
||||
<p
|
||||
style={{
|
||||
function Hero() {
|
||||
return (
|
||||
<section style={{
|
||||
background: "linear-gradient(135deg, #0D1117 0%, #0D2150 100%)",
|
||||
padding: "100px 40px", color: "#fff",
|
||||
}}>
|
||||
<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
|
||||
</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>
|
||||
<br />para recibir en Ecuador
|
||||
</h1>
|
||||
<p style={{ color: "#9CA3AF", maxWidth: 560, fontSize: "1.05rem", marginBottom: 16 }}>
|
||||
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.
|
||||
</p>
|
||||
<div style={{
|
||||
background: "rgba(0,87,255,.1)", border: "1px solid rgba(0,87,255,.25)",
|
||||
borderRadius: 10, padding: "14px 18px", marginBottom: 32,
|
||||
fontFamily: "monospace", fontSize: ".9rem", color: "#A5B4FC", maxWidth: 520,
|
||||
}}>
|
||||
📍 <strong style={{ color: "#FF6B00" }}>Tu Suite:</strong>{" "}
|
||||
150 N Day St, Suite EC-XXXXX, City of Orange, NJ 07050, EE.UU.
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
|
||||
<Link href="/registro" style={{
|
||||
background: "#0057FF", color: "#fff",
|
||||
padding: "14px 28px", borderRadius: 8, fontWeight: 700, fontSize: ".95rem",
|
||||
}}>
|
||||
Crear mi Suite gratis →
|
||||
</Link>
|
||||
<Link href="/calculadora" style={{
|
||||
border: "2px solid rgba(255,255,255,.2)", color: "#9CA3AF",
|
||||
padding: "12px 26px", borderRadius: 8, fontSize: ".9rem",
|
||||
}}>
|
||||
Calcular mi envío
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 32, marginTop: 48, flexWrap: "wrap" }}>
|
||||
{[
|
||||
["🏢", "Moraworld Imports S.A.S.", "Ecuador · RUC activo"],
|
||||
["🏭", "Mora Global Import LLC", "New Jersey, EE.UU."],
|
||||
["🛃", "Autorización SENAE", "Declaración automática"],
|
||||
].map(([icon, title, sub]) => (
|
||||
<div key={title} style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
|
||||
<span style={{ fontSize: "1.5rem" }}>{icon}</span>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: ".88rem", color: "#fff" }}>{title}</div>
|
||||
<div style={{ fontSize: ".78rem", color: "#6B7280" }}>{sub}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ServicesSection() {
|
||||
return (
|
||||
<section style={{ padding: "80px 40px", background: "#F3F4F6" }}>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto" }}>
|
||||
<div style={{ textAlign: "center", marginBottom: 48 }}>
|
||||
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>Dos servicios, un solo sistema</h2>
|
||||
<p style={{ color: "#6B7280", fontSize: ".95rem" }}>Casillero personal para compras online y carga pesada para importadores B2B.</p>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 }}>
|
||||
<div style={{ background: "#fff", borderRadius: 16, padding: 32, border: "2px solid #BFDBFE" }}>
|
||||
<div style={{ fontSize: "2.5rem", marginBottom: 12 }}>📦</div>
|
||||
<h3 style={{ fontWeight: 800, fontSize: "1.2rem", marginBottom: 8 }}>Casillero Personal</h3>
|
||||
<p style={{ color: "#6B7280", marginBottom: 16 }}>
|
||||
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>
|
||||
<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>
|
||||
<Link href="/casillero" style={{
|
||||
display: "inline-block", marginTop: 20, background: "#0057FF", color: "#fff",
|
||||
padding: "10px 20px", borderRadius: 8, fontWeight: 600, fontSize: ".88rem",
|
||||
}}>
|
||||
Conocer más →
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ background: "#fff", borderRadius: 16, padding: 32, border: "2px solid #FED7AA" }}>
|
||||
<div style={{ fontSize: "2.5rem", marginBottom: 12 }}>🚢</div>
|
||||
<h3 style={{ fontWeight: 800, fontSize: "1.2rem", marginBottom: 8 }}>Carga Pesada / Pallets B2B</h3>
|
||||
<p style={{ color: "#6B7280", marginBottom: 16 }}>
|
||||
Para importadores mayoristas: pallets, contenedores, volumen alto. Cotización personalizada,
|
||||
certificación INEN y declaración formal DAI ante la SENAE.
|
||||
</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>
|
||||
<Link href="/carga-pesada/cotizacion" style={{
|
||||
display: "inline-block", marginTop: 20, background: "#FF6B00", color: "#fff",
|
||||
padding: "10px 20px", borderRadius: 8, fontWeight: 600, fontSize: ".88rem",
|
||||
}}>
|
||||
Solicitar cotización →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ModulesSection() {
|
||||
return (
|
||||
<section style={{ padding: "80px 40px", background: "#fff" }}>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto" }}>
|
||||
<div style={{ textAlign: "center", marginBottom: 48 }}>
|
||||
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>8 módulos integrados</h2>
|
||||
<p style={{ color: "#6B7280" }}>Toda la operación logística cubierta en una sola plataforma.</p>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16 }}>
|
||||
{MODULES.map((m) => (
|
||||
<div key={m.title} style={{
|
||||
background: "#F9FAFB", borderRadius: 12, padding: 20,
|
||||
border: "1px solid #E5E7EB", transition: "box-shadow .2s",
|
||||
}}>
|
||||
<div style={{ fontSize: "1.8rem", marginBottom: 10 }}>{m.icon}</div>
|
||||
<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>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function TrackingSection() {
|
||||
return (
|
||||
<section style={{ padding: "80px 40px", background: "#F3F4F6" }}>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 48, alignItems: "start" }}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 12 }}>
|
||||
Tracking en tiempo real
|
||||
</h2>
|
||||
<p style={{ color: "#6B7280", marginBottom: 16 }}>
|
||||
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.
|
||||
</p>
|
||||
<div style={{
|
||||
background: "#0D1117", borderRadius: 10, padding: "14px 18px",
|
||||
fontFamily: "monospace", fontSize: ".88rem", color: "#A5B4FC",
|
||||
marginBottom: 20, border: "1px solid rgba(0,87,255,.3)",
|
||||
}}>
|
||||
Formato: <strong style={{ color: "#FF6B00" }}>EC-YYYYMMDD-XXXXXX</strong><br />
|
||||
Ejemplo: <strong>EC-20260506-082341</strong>
|
||||
</div>
|
||||
<Link href="/tracking" style={{
|
||||
background: "#0057FF", color: "#fff",
|
||||
padding: "12px 24px", borderRadius: 8, fontWeight: 600, fontSize: ".9rem",
|
||||
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,
|
||||
}}
|
||||
>
|
||||
Fase 0 — Fundación del software
|
||||
</p>
|
||||
<h1 style={{ fontSize: "clamp(2rem, 5vw, 3rem)", fontWeight: 800, lineHeight: 1.15, marginBottom: 16 }}>
|
||||
Tu casillero en <em style={{ color: "var(--accent)", fontStyle: "normal" }}>New Jersey</em>
|
||||
<br />
|
||||
para recibir en Ecuador
|
||||
</h1>
|
||||
<p style={{ color: "#9CA3AF", maxWidth: 520, marginBottom: 32 }}>
|
||||
Monorepo activo: Next.js + NestJS + PostgreSQL. Desarrollo local con Docker; despliegue en Coolify.
|
||||
</p>
|
||||
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
|
||||
<a
|
||||
href="/status"
|
||||
style={{
|
||||
background: "var(--primary)",
|
||||
color: "#fff",
|
||||
padding: "14px 28px",
|
||||
borderRadius: 8,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Ver estado del sistema →
|
||||
</a>
|
||||
<span
|
||||
style={{
|
||||
border: "2px solid rgba(255,255,255,.2)",
|
||||
color: "#9CA3AF",
|
||||
padding: "12px 26px",
|
||||
borderRadius: 8,
|
||||
fontSize: ".9rem",
|
||||
}}
|
||||
>
|
||||
Prototipos HTML en la raíz del repo
|
||||
</span>
|
||||
}}>
|
||||
Buscar mi paquete →
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
{TRACKING_STATES.map((s) => (
|
||||
<div key={s.num} style={{
|
||||
display: "flex", gap: 12, alignItems: "center",
|
||||
background: "#fff", borderRadius: 8, padding: "8px 14px",
|
||||
border: "1px solid #E5E7EB",
|
||||
}}>
|
||||
<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>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function CalculatorPreviewSection() {
|
||||
return (
|
||||
<section style={{ padding: "80px 40px", background: "#fff" }}>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto", textAlign: "center" }}>
|
||||
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>
|
||||
Calculadora SENAE gratuita
|
||||
</h2>
|
||||
<p style={{ color: "#6B7280", marginBottom: 40 }}>
|
||||
Sin registro. Conoce el costo total de tu envío antes de comprar.
|
||||
</p>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 16, marginBottom: 32 }}>
|
||||
{SENAE_CATEGORIES.map((c) => (
|
||||
<div key={c.name} style={{
|
||||
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={{ fontSize: "2.2rem", fontWeight: 900, color: c.color, lineHeight: 1, marginBottom: 8 }}>{c.rate}</div>
|
||||
<div style={{ fontSize: ".75rem", color: "#4B5563" }}>{c.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{
|
||||
background: "#0D1117", borderRadius: 12, padding: "20px 24px",
|
||||
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 }}>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 }}>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
|
||||
</div>
|
||||
<Link href="/calculadora" style={{
|
||||
background: "#0057FF", color: "#fff",
|
||||
padding: "14px 32px", borderRadius: 8, fontWeight: 700,
|
||||
display: "inline-block", fontSize: ".95rem",
|
||||
}}>
|
||||
Calcular mi envío →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RolesSection() {
|
||||
return (
|
||||
<section style={{ padding: "80px 40px", background: "#F3F4F6" }}>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto" }}>
|
||||
<div style={{ textAlign: "center", marginBottom: 48 }}>
|
||||
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>6 roles, accesos diferenciados</h2>
|
||||
<p style={{ color: "#6B7280" }}>Cada usuario tiene exactamente lo que necesita. Ni más, ni menos.</p>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 16 }}>
|
||||
{ROLES.map((r) => (
|
||||
<div key={r.name} style={{
|
||||
background: r.bg, borderRadius: 12, padding: 20,
|
||||
border: "1px solid #E5E7EB",
|
||||
}}>
|
||||
<div style={{ fontSize: "1.5rem", marginBottom: 10 }}>{r.icon}</div>
|
||||
<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>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function CompaniesSection() {
|
||||
return (
|
||||
<section style={{ padding: "80px 40px", background: "#fff" }}>
|
||||
<div style={{ maxWidth: 1100, margin: "0 auto", textAlign: "center" }}>
|
||||
<h2 style={{ fontSize: "1.8rem", fontWeight: 800, marginBottom: 8 }}>Operación legal en dos países</h2>
|
||||
<p style={{ color: "#6B7280", marginBottom: 40 }}>Empresas constituidas legalmente en Ecuador y EE.UU.</p>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, maxWidth: 800, margin: "0 auto" }}>
|
||||
{[
|
||||
{
|
||||
flag: "🇪🇨",
|
||||
name: "Moraworld Imports S.A.S.",
|
||||
detail: "Constituida en Ecuador. Representante legal en Cuenca. RUC activo, permisos aduaneros vigentes ante la SENAE.",
|
||||
badge: "Ecuador · S.A.S. · SENAE · Cuenca",
|
||||
},
|
||||
{
|
||||
flag: "🇺🇸",
|
||||
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.",
|
||||
badge: "New Jersey · LLC · NJ 07050",
|
||||
},
|
||||
].map((c) => (
|
||||
<div key={c.name} style={{
|
||||
background: "#fff", borderRadius: 16, padding: 28,
|
||||
border: "2px solid #E5E7EB", display: "flex", gap: 16, alignItems: "flex-start",
|
||||
}}>
|
||||
<span style={{ fontSize: "2.5rem" }}>{c.flag}</span>
|
||||
<div style={{ textAlign: "left" }}>
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<footer style={{
|
||||
background: "#0D1117", padding: "48px 40px", color: "rgba(255,255,255,.5)",
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<div style={{ fontSize: "1.1rem", fontWeight: 800, color: "#fff", marginBottom: 8 }}>
|
||||
Moraworld<span style={{ color: "#FF6B00" }}>.</span>Imports
|
||||
</div>
|
||||
<p style={{ fontSize: ".82rem", marginBottom: 20 }}>
|
||||
Moraworld Imports S.A.S. · Mora Global Import LLC
|
||||
</p>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "center", marginBottom: 24 }}>
|
||||
{["ISO/IEC 27001", "SENAE Autorizado", "INEN", "LOPDP Ecuador", "Multi-Tenant"].map((b) => (
|
||||
<span key={b} style={{
|
||||
background: "rgba(255,255,255,.07)", color: "rgba(255,255,255,.5)",
|
||||
fontSize: ".7rem", fontWeight: 700, padding: "4px 12px", borderRadius: 999,
|
||||
}}>{b}</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 24, justifyContent: "center", flexWrap: "wrap", fontSize: ".82rem" }}>
|
||||
<Link href="/como-funciona">Cómo funciona</Link>
|
||||
<Link href="/calculadora">Calculadora</Link>
|
||||
<Link href="/tracking">Tracking</Link>
|
||||
<Link href="/tarifas">Tarifas</Link>
|
||||
<Link href="/quienes-somos">Quiénes somos</Link>
|
||||
<Link href="/casillero">Casillero</Link>
|
||||
<Link href="/carga-pesada">Carga Pesada</Link>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Página principal ─────────────────────────────────────────
|
||||
|
||||
<section style={{ padding: "48px 24px", maxWidth: 1100, margin: "0 auto" }}>
|
||||
<h2 style={{ fontSize: "1.25rem", fontWeight: 700, marginBottom: 16 }}>Próximos pasos (Fase 1)</h2>
|
||||
<ul style={{ color: "var(--gray-500)", paddingLeft: 20, display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<li>Registro e inicio de sesión con JWT + MFA</li>
|
||||
<li>Asignación automática de Suite (EC-XXXXX)</li>
|
||||
<li>Pre-alertas y registro de paquetes</li>
|
||||
<li>Migración del portal desde portal-cliente.html</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<Hero />
|
||||
<ServicesSection />
|
||||
<ModulesSection />
|
||||
<TrackingSection />
|
||||
<CalculatorPreviewSection />
|
||||
<RolesSection />
|
||||
<CompaniesSection />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rastrear Paquete — Moraworld Imports",
|
||||
description: "Busca tu paquete con el Tracking ID (formato EC-YYYYMMDD-XXXXXX). Sin necesidad de iniciar sesión.",
|
||||
};
|
||||
|
||||
export default function TrackingPage() {
|
||||
return (
|
||||
<main style={{ minHeight: "100vh", background: "#F3F4F6", padding: "40px 20px" }}>
|
||||
<div style={{ maxWidth: 600, margin: "0 auto" }}>
|
||||
<div style={{ marginBottom: 32 }}>
|
||||
<Link href="/" style={{ color: "#6B7280", fontSize: ".88rem" }}>← Inicio</Link>
|
||||
<h1 style={{ fontSize: "1.8rem", fontWeight: 800, marginTop: 8, marginBottom: 8 }}>
|
||||
Rastrear paquete
|
||||
</h1>
|
||||
<p style={{ color: "#6B7280" }}>
|
||||
Ingresa tu Tracking ID para ver el estado de tu paquete. No necesitas iniciar sesión.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
background: "#fff", borderRadius: 16, padding: 32,
|
||||
border: "1px solid #E5E7EB", marginBottom: 24,
|
||||
}}>
|
||||
{/* Formulario — cliente component en Fase 1 */}
|
||||
<label style={{ display: "block", fontWeight: 600, marginBottom: 8, fontSize: ".9rem" }}>
|
||||
Tracking ID
|
||||
</label>
|
||||
<div style={{
|
||||
background: "#F3F4F6", borderRadius: 8, padding: "12px 16px",
|
||||
fontFamily: "monospace", color: "#9CA3AF", fontSize: ".9rem",
|
||||
border: "1px solid #E5E7EB", marginBottom: 8,
|
||||
}}>
|
||||
EC-20260506-082341
|
||||
</div>
|
||||
<p style={{ color: "#9CA3AF", fontSize: ".78rem", marginBottom: 20 }}>
|
||||
Formato: <strong style={{ fontFamily: "monospace" }}>EC-YYYYMMDD-XXXXXX</strong>
|
||||
</p>
|
||||
<div style={{
|
||||
background: "#EEF3FF", borderRadius: 8, padding: "12px 16px",
|
||||
fontSize: ".84rem", color: "#1D4ED8",
|
||||
}}>
|
||||
El formulario de búsqueda interactiva se habilita en Fase 1.
|
||||
Por ahora puedes usar la API: <code style={{ fontFamily: "monospace" }}>GET /api/tracking/:id</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
background: "#fff", borderRadius: 16, padding: 32,
|
||||
border: "1px solid #E5E7EB",
|
||||
}}>
|
||||
<h2 style={{ fontWeight: 700, marginBottom: 16, fontSize: "1rem" }}>
|
||||
11 estados del ciclo de vida
|
||||
</h2>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
{[
|
||||
{ num: "01", name: "REGISTRADO", color: "#6B7280", desc: "Cliente registró la compra" },
|
||||
{ num: "02", name: "EN_TRANSITO_BODEGA", color: "#F59E0B", desc: "Despachado hacia NJ" },
|
||||
{ num: "03", name: "RECIBIDO_BODEGA", color: "#3B82F6", desc: "Bodega NJ confirmó recepción" },
|
||||
{ num: "04", name: "EN_VERIFICACION", color: "#8B5CF6", desc: "Pesando y fotografiando" },
|
||||
{ num: "05", name: "VERIFICADO", color: "#10B981", desc: "Peso confirmado" },
|
||||
{ num: "06", name: "DECLARACION_ADUANERA", color: "#0057FF", desc: "DSI aprobada por SENAE" },
|
||||
{ num: "07", name: "EN_TRANSITO_ECUADOR", color: "#F97316", desc: "En vuelo a Ecuador" },
|
||||
{ num: "08", name: "EN_ADUANA_ECUADOR", color: "#EF4444", desc: "Inspección en aduana" },
|
||||
{ num: "09", name: "LISTO_ENTREGA", color: "#84CC16", desc: "Listo para entrega" },
|
||||
{ num: "10", name: "ENTREGADO", color: "#10B981", desc: "Entregado al cliente ✓" },
|
||||
{ num: "11", name: "INCIDENCIA", color: "#EF4444", desc: "Problema reportado ⚠" },
|
||||
].map((s) => (
|
||||
<div key={s.num} style={{
|
||||
display: "flex", gap: 10, alignItems: "center",
|
||||
padding: "8px 12px", background: "#F9FAFB",
|
||||
borderRadius: 6, border: "1px solid #E5E7EB",
|
||||
}}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: "50%", background: s.color, flexShrink: 0 }} />
|
||||
<span style={{ fontFamily: "monospace", fontSize: ".65rem", color: "#9CA3AF", minWidth: 18 }}>{s.num}</span>
|
||||
<span style={{ fontFamily: "monospace", fontSize: ".75rem", fontWeight: 700, flex: 1 }}>{s.name}</span>
|
||||
<span style={{ fontSize: ".72rem", color: "#6B7280" }}>{s.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user