diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..74af1be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,67 @@ +# ═══════════════════════════════════════════════════════════════ +# .dockerignore — Moraworld Imports +# ═══════════════════════════════════════════════════════════════ + +# Git +.git +.gitignore + +# Node / pnpm +node_modules +.pnpm-store +*.log +npm-debug.log* +pnpm-debug.log* + +# Build outputs +dist +build +.next +out +coverage +*.tsbuildinfo + +# IDE / OS +.vscode +.idea +*.swp +*.swo +.DS_Store +Thumbs.db + +# Environment +.env +.env.local +.env.*.local +*.pem + +# Test / CI +.nyc_output +*.lcov + +# Docker +Dockerfile* +docker-compose* +coolify-compose.yml +.dockerignore + +# Docs +*.md +!README.md + +# Turbo +.turbo + +# Uploads (runtime only) +uploads + +# Prisma +packages/database/prisma/migrations/* +!packages/database/prisma/migrations/.gitkeep + +# Misc +*.orig +*.bak +*.tmp +*.temp +.cache diff --git a/.env.example b/.env.example index c5aca63..5c36b72 100644 --- a/.env.example +++ b/.env.example @@ -1,132 +1,90 @@ -# ══════════════════════════════════════════════════════════════ -# Moraworld Imports — Variables de entorno -# Sincronizado con documentacion.html v1.1 -# ══════════════════════════════════════════════════════════════ +# ═══════════════════════════════════════════════════════════════ +# Moraworld Imports — Variables de entorno para Coolify (producción) +# ═══════════════════════════════════════════════════════════════ +# Copia este archivo a .env en Coolify → Project → Environment Variables +# ═══════════════════════════════════════════════════════════════ -# ─── Base de datos ─────────────────────────────────────────── -# Opción A — Coolify (PostgreSQL gestionado): -# DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/NOMBRE_BD?schema=moraworld" +# ─── Base ────────────────────────────────────────────────────── +NODE_ENV=production +NEXT_PUBLIC_API_URL=https://api.tu-dominio.com +NEXT_PUBLIC_WS_URL=https://api.tu-dominio.com + +# ─── Base de datos (Coolify managed PostgreSQL) ──────────────── +# Coolify inyecta DATABASE_URL automáticamente si creas un servicio Postgres +# y lo enlazas al proyecto. Formato: +# postgresql://usuario:password@host:puerto/database?schema=imports +DATABASE_URL=postgresql://moraworld:supersecret@postgres:5432/moraworld?schema=imports + +# ─── Redis (Coolify managed Redis) ──────────────────────────── +# Coolify inyecta REDIS_URL automáticamente. +# redis://:password@host:puerto/db +REDIS_URL=redis://:supersecret@redis:6379/2 + +# ─── Auth / JWT ─────────────────────────────────────────────── +# Genera con: openssl rand -base64 48 +JWT_SECRET=tu-jwt-secret-base64-de-48-caracteres-minimo +JWT_REFRESH_SECRET=tu-refresh-secret-base64-distinto-del-de-arriba +AUDIT_HMAC_SECRET=tu-hmac-secret-para-auditoria-32-chars-min + +# ─── CORS ────────────────────────────────────────────────────── +# URLs permitidas (separadas por coma, sin espacios al final) +CORS_ORIGINS=https://app.tu-dominio.com,https://www.tu-dominio.com + +# ─── Almacenamiento S3 / MinIO (opcional — para fotos/facturas) ──── +# Si no configuras S3_ENDPOINT, usa disco local (./uploads) +S3_ENDPOINT=https://s3.tu-proveedor.com +S3_ACCESS_KEY=minioadmin +S3_SECRET_KEY=minioadmin +S3_BUCKET=moraworld-imports +S3_REGION=us-east-1 +S3_PUBLIC_URL=https://cdn.tu-dominio.com + +# ─── Stripe (pagos — Checkout Session) ──────────────────────── +STRIPE_WEBHOOK_SECRET=whsec_... # De Stripe Dashboard → Webhooks + +# ─── Integraciones (claves se guardan en BD vía Admin → Integraciones) ── +# Estas NO van en .env; se configuran en la UI tras el primer deploy. +# Zeptomail, SENAE, Amazon SP-API, Twilio, WhatsApp Business, INEN, Couriers, etc. + +# ─── Opcional: Worker / Cron ────────────────────────────────── +# ENABLE_WORKER=true +# CRON_SENAE_POLL=0 */30 * * * * + +# ═══════════════════════════════════════════════════════════════ +# NOTAS DE DESPLIEGUE EN COOLIFY +# ═══════════════════════════════════════════════════════════════ # -# Opción B — Local con docker compose: -DATABASE_URL="postgresql://moraworld:moraworld_dev@localhost:5432/moraworld?schema=public" - -# ─── Redis (colas de notificaciones, sesiones) ─────────────── -# Opción A — Coolify: -# REDIS_URL="redis://:PASSWORD@HOST:6379" -# REDIS_URL="rediss://:PASSWORD@HOST:6380" # si TLS +# 1. Crea un Project en Coolify → "New Resource" → "Docker Compose" +# Apunta a este repo, archivo: coolify-compose.yml # -# Opción B — Local: -REDIS_URL="redis://localhost:6379" - -# ─── API (NestJS) ──────────────────────────────────────────── -API_PORT=3001 -API_URL="http://localhost:3001" - -# JWT — Autenticación OAuth 2.0 + JWT + MFA (doc §05) -JWT_SECRET="change-me-in-production-min-32-chars-long-abc123" -JWT_EXPIRES_IN="15m" -JWT_REFRESH_EXPIRES_IN="7d" - -# ─── Web (Next.js) ─────────────────────────────────────────── -WEB_PORT=3000 -NEXT_PUBLIC_API_URL="http://localhost:3001" - -# ─── CORS (coma separada; en Coolify usar dominio real) ────── -CORS_ORIGINS="http://localhost:3000" - -# ─── Moraworld — Bodega NJ (doc §02) ───────────────────────── -# Dirección: 150 N Day St, City of Orange, NJ 07050, EE.UU. -WAREHOUSE_ADDRESS_STREET="150 N Day St" -WAREHOUSE_ADDRESS_CITY="City of Orange" -WAREHOUSE_ADDRESS_STATE="NJ" -WAREHOUSE_ADDRESS_ZIP="07050" -WAREHOUSE_ADDRESS_COUNTRY="US" - -# ─── Amazon SP-API (doc §14 — registro por enlace) ─────────── -# https://developer-docs.amazon.com/sp-api/docs -AMAZON_SP_CLIENT_ID="" -AMAZON_SP_CLIENT_SECRET="" -AMAZON_SP_REFRESH_TOKEN="" -AMAZON_SP_MARKETPLACE_ID="ATVPDKIKX0DER" # US marketplace - -# ─── WebService SENAE (doc §11 — declaración aduanera) ─────── -# SOAP / REST — Portal electrónico aduana Ecuador -SENAE_ENDPOINT="https://declaraciones.aduana.gob.ec/wsDeclaraciones" -SENAE_USERNAME="" -SENAE_PASSWORD="" -SENAE_CERT_PATH="" # Certificado digital PEM -SENAE_CERT_PASSPHRASE="" - -# ─── Pasarela de pagos (doc §09 paso 8 / §18) ──────────────── -# Stripe (internacional) -STRIPE_SECRET_KEY="" -STRIPE_WEBHOOK_SECRET="" -NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="" - -# PayPhone (Ecuador) -PAYPHONE_APP_ID="" -PAYPHONE_TOKEN="" -PAYPHONE_STORE_ID="" - -# ─── WhatsApp Business API (doc §16 / §18) ─────────────────── -# Notificaciones automáticas + botón flotante de contacto -WHATSAPP_API_URL="https://graph.facebook.com/v19.0" -WHATSAPP_PHONE_NUMBER_ID="" -WHATSAPP_ACCESS_TOKEN="" -# Número NJ (operaciones) y Cuenca (aduana) — doc §21 -WHATSAPP_NJ_NUMBER="+15550000001" -WHATSAPP_CUENCA_NUMBER="+5939900000001" - -# ─── Email (doc §16) ───────────────────────────────────────── -# SendGrid -SENDGRID_API_KEY="" -EMAIL_FROM="noreply@moraworldimports.com" - -# AWS SES (alternativa) -# AWS_SES_REGION="us-east-1" -# AWS_SES_ACCESS_KEY="" -# AWS_SES_SECRET_KEY="" - -# ─── SMS Gateway (doc §16) ─────────────────────────────────── -SMS_PROVIDER="twilio" # twilio | vonage -TWILIO_ACCOUNT_SID="" -TWILIO_AUTH_TOKEN="" -TWILIO_PHONE_NUMBER="" - -# ─── Couriers internacionales (doc §18) ────────────────────── -# FedEx -FEDEX_CLIENT_ID="" -FEDEX_CLIENT_SECRET="" -FEDEX_ACCOUNT_NUMBER="" - -# DHL -DHL_API_KEY="" -DHL_API_SECRET="" - -# UPS -UPS_CLIENT_ID="" -UPS_CLIENT_SECRET="" - -# ─── S3 / Almacenamiento de archivos (doc §05) ─────────────── -# Fotos de paquetes, facturas, documentos SENAE -# AWS S3 -S3_BUCKET="moraworld-files" -S3_REGION="us-east-1" -S3_ACCESS_KEY="" -S3_SECRET_KEY="" - -# MinIO local (docker-compose — desarrollo): -# S3_ENDPOINT="http://localhost:9000" -# S3_BUCKET="moraworld" -# S3_ACCESS_KEY="minioadmin" -# S3_SECRET_KEY="minioadmin" -# S3_REGION="us-east-1" -# S3_FORCE_PATH_STYLE="true" - -# ─── SIEM / Monitoreo (doc §05 ISO A.12) ───────────────────── -# Datadog / New Relic / Sentry -SENTRY_DSN="" -# DATADOG_API_KEY="" - -# ─── Entorno ───────────────────────────────────────────────── -NODE_ENV="development" # development | staging | production +# 2. Añade servicios gestionados: +# - PostgreSQL 16 (nombre lógico: postgres) +# - Redis 7 (nombre lógico: redis) +# Coolify inyectará DATABASE_URL y REDIS_URL automáticamente. +# +# 3. En "Environment Variables" del proyecto, pega el contenido +# de este archivo (ajusta valores reales). +# +# 4. Dominios (Coolify → Project → Domains): +# - api.tu-dominio.com → servicio: api, puerto: 3001 +# - app.tu-dominio.com → servicio: web, puerto: 3000 +# - ws.tu-dominio.com → servicio: api, puerto: 3001 (WebSocket) +# Coolify genera certificados TLS automáticos (Let's Encrypt). +# +# 5. WebSocket: el gateway corre en /ws bajo el mismo puerto 3001. +# Configura el dominio ws.tu-dominio.com apuntando al servicio api. +# +# 6. Health checks: +# - API: GET /api/health +# - Web: GET /status +# +# 7. Primer deploy → entra a https://app.tu-dominio.com/admin/configuracion +# y guarda las claves de integración (Zeptomail, SENAE, Stripe, etc.). +# +# 8. Stripe Webhook URL en Dashboard: +# https://api.tu-dominio.com/api/payments/webhook?tenant=mi-tenant-slug +# +# 9. Para actualizar: push a main → Coolify auto-deploy (si configurado) +# o "Deploy" manual en la UI. +# +# ═══════════════════════════════════════════════════════════════ diff --git a/COOLIFY.md b/COOLIFY.md new file mode 100644 index 0000000..837deeb --- /dev/null +++ b/COOLIFY.md @@ -0,0 +1,193 @@ +# Despliegue en Coolify — Moraworld Imports + +> **TL;DR** — Coolify gestiona Postgres + Redis. Solo necesitas el repo, `coolify-compose.yml` y las variables de `.env.example`. + +--- + +## 1️⃣ Requisitos previos + +- Servidor con **Docker 24+** y **Coolify** instalado (`curl -fsSL https://get.coolify.io | bash`) +- Dominio propio con DNS apuntando al servidor (A/AAAA + wildcard `*.tu-dominio.com`) +- Cuenta GitHub/GitLab con el repo clonado + +--- + +## 2️⃣ Crear Project en Coolify + +1. **Projects → New Project** → nombre: `moraworld-imports` +2. **Resources → New → Docker Compose** + - **Repository**: `https://github.com/lizandrogd/moraworld-imports` + - **Branch**: `main` + - **Compose file**: `coolify-compose.yml` + - **Build pack**: `Dockerfile` (auto-detectado) + +--- + +## 3️⃣ Servicios gestionados (managed services) + +En el projecto → **Services → Add Service**: + +| Servicio | Tipo | Nombre lógico | Notas | +|----------|------|---------------|-------| +| **PostgreSQL** | `PostgreSQL 16` | `postgres` | Coolify inyecta `DATABASE_URL` | +| **Redis** | `Redis 7` | `redis` | Coolify inyecta `REDIS_URL` | +| **MinIO** (opcional) | `MinIO` | `minio` | Solo si no usas S3 externo; expón puertos 9000/9001 | + +> **Importante**: Los nombres lógicos (`postgres`, `redis`) deben coincidir con los hostnames usados en `DATABASE_URL`/`REDIS_URL` si los escribes a mano. Coolify los resuelve automáticamente. + +--- + +## 4️⃣ Variables de entorno + +En **Project → Environment Variables**, añade **una por una** (no pegues el bloque completo): + +| Variable | Ejemplo / Generación | +|----------|----------------------| +| `NODE_ENV` | `production` | +| `NEXT_PUBLIC_API_URL` | `https://api.tu-dominio.com` | +| `NEXT_PUBLIC_WS_URL` | `https://api.tu-dominio.com` | +| `JWT_SECRET` | `openssl rand -base64 48` | +| `JWT_REFRESH_SECRET` | `openssl rand -base64 48` (distinto) | +| `AUDIT_HMAC_SECRET` | `openssl rand -hex 32` | +| `CORS_ORIGINS` | `https://app.tu-dominio.com,https://www.tu-dominio.com` | +| `S3_ENDPOINT` | `https://s3.tu-proveedor.com` (o vacío para disco local) | +| `S3_ACCESS_KEY` | `minioadmin` | +| `S3_SECRET_KEY` | `minioadmin` | +| `S3_BUCKET` | `moraworld-imports` | +| `S3_REGION` | `us-east-1` | +| `S3_PUBLIC_URL` | `https://cdn.tu-dominio.com` | +| `STRIPE_WEBHOOK_SECRET` | `whsec_...` (de Stripe Dashboard) | + +> **No pongas** claves de Zeptomail, SENAE, Amazon, Twilio, WhatsApp, etc. aquí. Se configuran en **Admin → Integraciones** tras el primer deploy. + +--- + +## 5️⃣ Dominios y TLS + +En **Project → Domains** añade: + +| Dominio | Servicio | Puerto | TLS | +|---------|----------|--------|-----| +| `api.tu-dominio.com` | `api` | `3001` | ✅ Auto | +| `app.tu-dominio.com` | `web` | `3000` | ✅ Auto | +| `ws.tu-dominio.com` | `api` | `3001` | ✅ Auto (WebSocket) | + +Coolify genera certificados Let's Encrypt automáticos y renueva cada 60 días. + +> **WebSocket**: el gateway corre en `/ws` bajo el mismo puerto 3001. El dominio `ws.tu-dominio.com` apunta al mismo servicio `api`. + +--- + +## 6️⃣ Health Checks + +Coolify usa los health checks del `docker-compose` para saber cuándo el servicio está listo: + +- **API**: `GET http://localhost:3001/api/health` → 200 OK +- **Web**: `GET http://localhost:3000/status` → 200 OK (crea `apps/web/public/status.txt` con `ok` si no existe) + +Si el health check falla, Coolify reintenta según `retries` y `start_period`. + +--- + +## 7️⃣ Primer deploy y post-configuración + +1. **Deploy** → espera a que ambos servicios estén "Healthy" +2. Entra a `https://app.tu-dominio.com` +3. Regístrate (primer usuario = `SUPER_ADMIN` automático si BD vacía) +4. Ve a **Admin → Configuración → Integraciones** y guarda: + - **Zeptomail**: `zeptomail_api_key`, `email_from`, `email_from_name` + - **SENAE**: `senae_endpoint`, `senae_api_key`, `senae_ruc`, `senae_agent_code` + - **Stripe**: `stripe_public_key`, `stripe_secret_key`, `stripe_webhook_secret` + - **Amazon SP-API**: `amazon_client_id`, `amazon_client_secret`, `amazon_refresh_token` + - **Twilio SMS**: `sms_account_sid`, `sms_auth_token`, `sms_from_number` + - **WhatsApp Business**: `whatsapp_api_token`, `whatsapp_phone_number_id` + - **Couriers** (FedEx/DHL/UPS), **INEN**, **Bodega**, etc. según necesites +5. **Stripe Webhook** en Dashboard → `https://api.tu-dominio.com/api/payments/webhook?tenant=mi-tenant-slug` + - Eventos: `checkout.session.completed`, `checkout.session.expired` + +--- + +## 8️⃣ Actualizaciones + +**Auto-deploy** (recomendado): +- Project → Settings → **Auto Deploy** = ON +- Push a `main` → Coolify detecta, build y despliega sin downtime (rolling update) + +**Manual**: +- Project → Deploy → **Force rebuild** si cambiaste Dockerfile o dependencias + +--- + +## 9️⃣ Backups y recuperación + +- **PostgreSQL**: Coolify → Service `postgres` → Backups → Schedule (diario/semanal) +- **Redis**: No crítico (cache/sesiones), pero puedes snapshottear +- **MinIO**: Versiona el bucket o replica a otro S3 +- **Código**: GitHub es la fuente de verdad + +Para restaurar: detén servicios → restaura dump SQL → `pnpm prisma db push` → reinicia. + +--- + +## 🔟 Troubleshooting rápido + +| Síntoma | Causa probable | Solución | +|---------|----------------|----------| +| API 502 / health fail | `DATABASE_URL` mal formada | Revisa variable; usa hostname `postgres` | +| WebSocket no conecta | Dominio `ws.` no apunta a `api` | Añade dominio `ws.tu-dominio.com` → servicio `api` | +| CORS error | `CORS_ORIGINS` sin tu frontend | Añade `https://app.tu-dominio.com` | +| Stripe webhook 404 | URL mal escrita en Dashboard | Debe ser `/api/payments/webhook?tenant=` | +| Prisma migrate fail | `prisma db push` en prod | Usa `prisma migrate deploy` en CI si migras schema | +| Uploads 404 | `S3_ENDPOINT` vacío pero archivos en disco | Configura MinIO/S3 o sirve `/uploads` vía reverse proxy | + +--- + +## 📁 Estructura de archivos Coolify + +``` +moraworld-imports/ +├── coolify-compose.yml # ← Coolify usa este archivo +├── .env.example # ← Copia valores a Coolify UI +├── apps/ +│ ├── api/Dockerfile # Multi-stage, standalone node +│ └── web/Dockerfile # Next.js standalone output +├── packages/database/ # Prisma schema +└── COOLIFY.md # ← Este archivo +``` + +--- + +## 🔐 Secrets management + +Coolify encripta las variables marcadas como **Secret** en la UI. +Para rotar secrets: +1. Genera nuevo valor local +2. Actualiza en Coolify → Environment Variables +3. Redeploy (rolling, cero downtime) + +--- + +## 📊 Monitoring básico + +- **Logs**: Coolify → Service → Logs (stdout/stderr) +- **Metrics**: Coolify → Service → Metrics (CPU, RAM, Network) +- **Uptime**: Configura Uptime Kuma / Better Uptime apuntando a `/api/health` y `/status` + +--- + +## 🚀 Checklist pre-producción + +- [ ] `JWT_SECRET` y `JWT_REFRESH_SECRET` únicos, 48+ chars base64 +- [ ] `AUDIT_HMAC_SECRET` 32+ chars hex +- [ ] `CORS_ORIGINS` solo tus dominios reales (sin `*`) +- [ ] `DATABASE_URL` apunta a Postgres gestionado por Coolify +- [ ] `REDIS_URL` apunta a Redis gestionado por Coolify +- [ ] Dominios `api.`, `app.`, `ws.` creados en Coolify con TLS +- [ ] Health checks pasan en ambos servicios +- [ ] Integraciones guardadas en Admin UI +- [ ] Stripe webhook registrado y probado +- [ ] Backup automático de Postgres configurado + +--- + +**¿Problemas?** Revisa logs en Coolify → Service → Logs. La mayoría son variables de entorno mal escritas o health checks que tardan más de `start_period`. \ No newline at end of file diff --git a/apps/web/public/status.txt b/apps/web/public/status.txt new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/apps/web/public/status.txt @@ -0,0 +1 @@ +ok diff --git a/coolify-compose.yml b/coolify-compose.yml new file mode 100644 index 0000000..e27c3f5 --- /dev/null +++ b/coolify-compose.yml @@ -0,0 +1,96 @@ +# Coolify Deployment — docker-compose for production (managed DB/Redis by Coolify) + +services: + api: + image: ghcr.io/lizandrogd/moraworld-imports/api:latest + build: + context: . + dockerfile: apps/api/Dockerfile + args: + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} + container_name: moraworld-api + restart: unless-stopped + ports: + - "3001:3001" + environment: + - NODE_ENV=production + - PORT=3001 + - DATABASE_URL=${DATABASE_URL} + - REDIS_URL=${REDIS_URL} + - JWT_SECRET=${JWT_SECRET} + - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET} + - AUDIT_HMAC_SECRET=${AUDIT_HMAC_SECRET} + - CORS_ORIGINS=${CORS_ORIGINS} + - S3_ENDPOINT=${S3_ENDPOINT} + - S3_ACCESS_KEY=${S3_ACCESS_KEY} + - S3_SECRET_KEY=${S3_SECRET_KEY} + - S3_BUCKET=${S3_BUCKET} + - S3_REGION=${S3_REGION} + - S3_PUBLIC_URL=${S3_PUBLIC_URL} + - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - moraworld-network + deploy: + resources: + limits: + cpus: '1.0' + memory: 1G + reservations: + cpus: '0.5' + memory: 512M + + web: + image: ghcr.io/lizandrogd/moraworld-imports/web:latest + build: + context: . + dockerfile: apps/web/Dockerfile + args: + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} + container_name: moraworld-web + restart: unless-stopped + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - PORT=3000 + - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} + - NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL} + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/status"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - moraworld-network + deploy: + resources: + limits: + cpus: '1.0' + memory: 1G + reservations: + cpus: '0.5' + memory: 512M + + # Worker opcional: jobs asíncronos (ej. polling couriers, cron SENAE) + # worker: + # image: ghcr.io/lizandrogd/moraworld-imports/api:latest + # command: ["node", "dist/worker.js"] + # environment: *api_env + # networks: [moraworld-network] + # deploy: + # resources: + # limits: + # cpus: '0.5' + # memory: 512M + +networks: + moraworld-network: + driver: bridge + name: moraworld-network