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:
Lizandro Guarnizo
2026-06-01 08:25:01 -05:00
parent 094ea9cf81
commit 93db76897d
26 changed files with 4651 additions and 201 deletions
+86
View File
@@ -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: "015%", 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>
);
}