This commit is contained in:
David Arranz 2025-12-15 19:21:09 +01:00
parent 401292539b
commit c9fe5847da
30 changed files with 170 additions and 135 deletions

View File

@ -111,4 +111,4 @@ COPY --from=builder /repo/apps/server/package.json ./apps/server/package.json
EXPOSE 3002
#CMD ["pnpm","exec", "node", "--env-file=apps/server/dist/.env", "apps/server/dist/index.js"]
CMD ["pnpm","exec", "node", "apps/server/dist/index.js"]
CMD ["pnpm", "exec", "node", "apps/server/dist/index.js"]

View File

@ -8,7 +8,6 @@ API_PORT=3002
NODE_ENV=production
HOST=0.0.0.0
PORT=3002
FRONTEND_URL=https://aana.factuges.app

View File

@ -1,6 +1,5 @@
NODE_ENV=development
HOST=0.0.0.0
SERVER_PORT=3002
API_PORT=3002
FRONTEND_URL=http://localhost:5173

View File

@ -2,8 +2,7 @@
# Core del servidor HTTP
# ───────────────────────────────
NODE_ENV=development
HOST=0.0.0.0
SERVER_PORT=3002
API_PORT=3002
# URL pública del frontend (CORS).
# En dev se puede permitir todo con '*'

View File

@ -13,7 +13,6 @@ DB_ROOT_PASS=verysecret
TRAEFIK_ENTRYPOINT=web
NODE_ENV=production
HOST=0.0.0.0
PORT=3002
FRONTEND_URL=http://factuges.rodax-software.local

45
apps/server/.env.rodax Normal file
View File

@ -0,0 +1,45 @@
# ───────────────────────────────
# Identidad de la compañía
# ───────────────────────────────
COMPANY=rodax
CTE_COMPANY_ID=5e4dc5b3-96b9-4968-9490-14bd032fec5f
# Dominios
DOMAIN=factuges.rodax-software.local
# ───────────────────────────────
# Base de datos (Sequelize / MySQL-MariaDB)
# ───────────────────────────────
DB_ROOT_PASS=verysecret
DB_USER=rodax_usr
DB_PASS=supersecret
DB_NAME=rodax_db
DB_PORT=3306
# Log de Sequelize (true|false)
DB_LOGGING=false
# Alterar estructura BD
DB_SYNC_MODE=none # none | alter | force
# ───────────────────────────────
# API
# ───────────────────────────────
API_PORT=3002
API_IMAGE=factuges-server:rodax-latest
# Plantillas
TEMPLATES_PATH=/repo/apps/server/templates
# Chrome executable path (Puppeteer)
PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome
# URL pública del frontend (CORS)
FRONTEND_URL=factuges.rodax-software.local
# Tiempo máximo para cada warmup() de un módulo, en milisegundos.
WARMUP_TIMEOUT_MS=10000
# Si es true, un fallo de warmup aborta el arranque. Si es false, continúa con warning.
WARMUP_STRICT=false

View File

@ -1,6 +1,6 @@
{
"name": "@erp/factuges-server",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"scripts": {
"build": "tsup src/index.ts --config tsup.config.ts",

View File

@ -47,7 +47,7 @@ export function createApp(): Application {
app.use(cors(ENV.NODE_ENV === "development" ? devCors : prodCors));
app.options("*", cors(ENV.NODE_ENV === "development" ? devCors : prodCors));
app.set("port", process.env.SERVER_PORT ?? 3002);
app.set("port", process.env.API_PORT ?? 3002);
// Oculta la cabecera x-powered-by
app.disable("x-powered-by");

View File

@ -13,8 +13,7 @@ const NODE_ENV = (process.env.NODE_ENV as NodeEnv) ?? "development";
const isProd = NODE_ENV === "production";
const isDev = NODE_ENV === "development";
const HOST = process.env.HOST ?? "0.0.0.0";
const SERVER_PORT = asNumber(process.env.SERVER_PORT, 3002);
const API_PORT = asNumber(process.env.API_PORT, 3002);
// En producción exigimos FRONTEND_URL definido (según requisitos actuales).
const FRONTEND_URL = isProd
@ -48,8 +47,7 @@ const TRUST_PROXY = asNumber(process.env.TRUST_PROXY, 0);
export const ENV = {
NODE_ENV,
HOST,
SERVER_PORT,
API_PORT,
FRONTEND_URL,
DATABASE_URL,
DB_DIALECT,

View File

@ -20,8 +20,8 @@ z.config(z.locales.es());
export const currentState = {
launchedAt: DateTime.now(),
appPath: process.cwd(),
host: ENV.HOST,
port: ENV.SERVER_PORT,
hosts: "0.0.0.0",
port: ENV.API_PORT,
environment: ENV.NODE_ENV,
connections: {} as Record<string, unknown>,
};
@ -91,12 +91,11 @@ const serverError = (error: NodeJS.ErrnoException) => {
logger.error("⛔️ Server wasn't able to start properly.", {
label: "serverError0",
error,
host: ENV.HOST,
port: ENV.SERVER_PORT,
port: ENV.API_PORT,
});
if (error.code === "EADDRINUSE") {
logger.error(`Port ${ENV.SERVER_PORT} already in use`, { error, label: "serverError1" });
logger.error(`Port ${ENV.API_PORT} already in use`, { error, label: "serverError1" });
} else {
logger.error(error.message, { error, label: "serverError2" });
}
@ -211,8 +210,7 @@ process.on("uncaughtException", async (error: Error) => {
// Mostrar variables de entorno
logger.info(`Environment: ${currentState.environment}`);
logger.info(`HOST: ${ENV.HOST}`);
logger.info(`SERVER_PORT: ${ENV.SERVER_PORT}`);
logger.info(`API_PORT: ${ENV.API_PORT}`);
logger.info(`API_BASE_PATH: ${API_BASE_PATH}`);
logger.info(`FRONTEND_URL: ${ENV.FRONTEND_URL}`);
@ -268,7 +266,6 @@ process.on("uncaughtException", async (error: Error) => {
// URLs de acceso útiles
logger.info(`⚡️ Server accessible at: http://localhost:${currentState.port}`);
logger.info(`⚡️ Server accessible at: http://${currentState.host}:${currentState.port}`);
for (const address of addresses) {
logger.info(`⚡️ Server accessible at: http://${address}:${currentState.port}`);
}

View File

@ -1,7 +1,7 @@
{
"name": "@erp/factuges-web",
"private": true,
"version": "0.1.0",
"version": "0.1.1",
"type": "module",
"scripts": {
"dev": "vite --host --clearScreen false",

View File

@ -1,6 +1,6 @@
{
"name": "@erp/auth",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -7,8 +7,8 @@ export function mockUser(req: RequestWithAuth, _res: Response, next: NextFunctio
req.user = {
userId: UniqueID.create("9e4dc5b3-96b9-4968-9490-14bd032fec5f").data,
email: EmailAddress.create("dev@example.com").data,
companyId: UniqueID.create("019a9667-6a65-767a-a737-48234ee50a3a").data,
companySlug: "alonsoysal",
companyId: UniqueID.create("5e4dc5b3-96b9-4968-9490-14bd032fec5f").data,
companySlug: "rodax",
roles: ["admin"],
};

View File

@ -1,6 +1,6 @@
{
"name": "@erp/core",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -12,24 +12,25 @@
// Nota: Todos los nombres de tipos/clases/archivos en inglés; comentarios en castellano.
import {
DomainValidationError,
type DomainValidationError,
type ValidationErrorCollection,
isDomainValidationError,
isValidationErrorCollection,
ValidationErrorCollection,
} from "@repo/rdx-ddd";
import {
DuplicateEntityError,
EntityNotFoundError,
type DuplicateEntityError,
type EntityNotFoundError,
isDuplicateEntityError,
isEntityNotFoundError,
} from "../../domain";
import {
InfrastructureRepositoryError,
InfrastructureUnavailableError,
type InfrastructureRepositoryError,
type InfrastructureUnavailableError,
isInfrastructureRepositoryError,
isInfrastructureUnavailableError,
} from "../errors";
import {
ApiError,
ConflictApiError,

View File

@ -1,7 +1,13 @@
import { Criteria } from "@repo/rdx-criteria/server";
import { NextFunction, Request, Response } from "express";
import { ApiError, ForbiddenApiError, UnauthorizedApiError, ValidationApiError } from "./errors";
import { ExpressController } from "./express-controller";
import type { Criteria } from "@repo/rdx-criteria/server";
import type { NextFunction, Request, Response } from "express";
import {
type ApiError,
ForbiddenApiError,
UnauthorizedApiError,
ValidationApiError,
} from "./errors";
import type { ExpressController } from "./express-controller";
export type GuardResultLike = { isFailure: boolean; error?: ApiError };

View File

@ -1,6 +1,6 @@
{
"name": "@erp/customer-invoices",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,6 +1,6 @@
{
"name": "@erp/customers",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,6 +1,6 @@
{
"name": "@erp/doc-numbering",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-criteria",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-ddd",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-logger",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-utils",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -42,6 +42,17 @@ fi
COMPANY="$1"
SSH_USER="rodax"
SSH_HOST="vps-2.rodax-software.com"
SSH_PORT="49152"
# Override por compañía específica
if [[ "$COMPANY" == "rodax" ]]; then
SSH_USER="rodax"
SSH_HOST="factuges.rodax-software.local"
SSH_PORT="22"
fi
# --- Parseo de flags ---
shift # quitamos el <company>, ahora solo quedan flags
@ -221,19 +232,26 @@ EOF
echo "📦 API manifest generado en ${OUT_API_DIR}/manifest-v${API_VERSION}-${DATE}.json"
fi
if [[ "$LOAD" == true ]]; then
echo "📥 Cargando imagen en producción vps-2.rodax-software.com..."
[[ "$MODE" == "web" || "$MODE" == "all" ]] && scp -r -P 49152 ${OUT_WEB_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
[[ "$MODE" == "api" || "$MODE" == "all" ]] && scp -r -P 49152 ${OUT_API_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
[[ "$MODE" == "api" || "$MODE" == "all" ]] && scp -r -P 49152 ${TEMPLATES_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
[[ "$MODE" == "api" || "$MODE" == "all" ]] && RESULT=$(ssh -p 49152 rodax@vps-2.rodax-software.com "docker load -i /opt/factuges/${COMPANY}/api/$(basename ${TAR_FILE_LATEST})")
[[ "$MODE" == "api" || "$MODE" == "all" ]] && echo $RESULT
#docker load -i "${TAR_FILE_V}"
if [[ "$LOAD" == true ]]; then
echo "📥 Cargando imagen en producción ${SSH_HOST}..."
[[ "$MODE" == "web" || "$MODE" == "all" ]] && scp -r -P "${SSH_PORT}" "${OUT_WEB_DIR}" "${SSH_USER}@${SSH_HOST}:/opt/factuges/${COMPANY}/"
[[ "$MODE" == "api" || "$MODE" == "all" ]] && scp -r -P "${SSH_PORT}" "${OUT_API_DIR}" "${SSH_USER}@${SSH_HOST}:/opt/factuges/${COMPANY}/"
[[ "$MODE" == "api" || "$MODE" == "all" ]] && scp -r -P "${SSH_PORT}" "${TEMPLATES_DIR}" "${SSH_USER}@${SSH_HOST}:/opt/factuges/${COMPANY}/"
if [[ "$MODE" == "api" || "$MODE" == "all" ]]; then
RESULT=$(ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" \
"docker load -i /opt/factuges/${COMPANY}/api/$(basename "${TAR_FILE_LATEST}")")
echo "${RESULT}"
fi
echo "✅ Imagen cargada en producción"
fi
# =====================================================
# 3⃣ Resumen
# =====================================================

View File

@ -1,78 +0,0 @@
services:
# MongoDB database for persistent storage (optional - SQLite is used by default)
mongodb:
image: mongo:8.0
container_name: caddymanager-mongodb
restart: unless-stopped
environment:
- MONGO_INITDB_ROOT_USERNAME=mongoadmin
- MONGO_INITDB_ROOT_PASSWORD=someSecretPassword # Change for production!
ports:
- "27017:27017" # Expose for local dev, remove for production
volumes:
- mongodb_data:/data/db
networks:
- caddymanager
profiles:
- mongodb # Use 'docker-compose --profile mongodb up' to include MongoDB
# Backend API server
backend:
image: caddymanager/caddymanager-backend:latest
container_name: caddymanager-backend
restart: unless-stopped
environment:
- PORT=3000
# Database Engine Configuration (defaults to SQLite)
- DB_ENGINE=sqlite # Options: 'sqlite' or 'mongodb'
# SQLite Configuration (used when DB_ENGINE=sqlite)
- SQLITE_DB_PATH=/app/data/caddymanager.sqlite
# MongoDB Configuration (used when DB_ENGINE=mongodb)
- MONGODB_URI=mongodb://mongoadmin:someSecretPassword@mongodb:27017/caddymanager?authSource=admin
- CORS_ORIGIN=http://localhost:80
- LOG_LEVEL=debug
- CADDY_SANDBOX_URL=http://localhost:2019
- PING_INTERVAL=30000
- PING_TIMEOUT=2000
- AUDIT_LOG_MAX_SIZE_MB=100
- AUDIT_LOG_RETENTION_DAYS=90
- METRICS_HISTORY_MAX=1000 # Optional: max number of in-memory metric history snapshots to keep
- JWT_SECRET=your_jwt_secret_key_here # Change for production!
- JWT_EXPIRATION=24h
# Backend is now only accessible through frontend proxy
volumes:
- sqlite_data:/app/data # SQLite database storage
networks:
- caddymanager
# Frontend web UI
frontend:
image: caddymanager/caddymanager-frontend:latest
container_name: caddymanager-frontend
restart: unless-stopped
depends_on:
- backend
environment:
- BACKEND_HOST=backend:3000
- APP_NAME=Caddy Manager
- DARK_MODE=true
ports:
- "80:80" # Expose web UI
networks:
- caddymanager
networks:
caddymanager:
driver: bridge
volumes:
mongodb_data: # Only used when MongoDB profile is active
sqlite_data: # SQLite database storage
# Notes:
# - SQLite is the default database engine - no additional setup required!
# - To use MongoDB instead, set DB_ENGINE=mongodb and start with: docker-compose --profile mongodb up
# - For production, use strong passwords and consider secrets management.
# - The backend uses SQLite by default, storing data in a persistent volume.
# - The frontend proxies all /api/* requests to the backend service.
# - Backend is not directly exposed - all API access goes through the frontend proxy.

View File

@ -67,7 +67,7 @@ services:
environment:
NODE_ENV: production
COMPANY: ${COMPANY}
PORT: ${SERVER_PORT:-3002}
PORT: ${API_PORT:-3002}
DB_DIALECT: "mysql"
DB_HOST: "db"
DB_PORT: ${DB_PORT}
@ -79,7 +79,7 @@ services:
- internal
- edge
ports:
- ${SERVER_PORT:-3002}:${SERVER_PORT:-3002}
- ${API_PORT:-3002}:${API_PORT:-3002}
labels:
traefik.enable: "true"
traefik.http.routers.factuges_rodax_api.rule: Host(`${DOMAIN}`) && PathPrefix(`/api`)

View File

@ -73,7 +73,7 @@ services:
environment:
NODE_ENV: production
COMPANY: ${COMPANY}
PORT: ${SERVER_PORT:-3002}
PORT: ${API_PORT:-3002}
DB_DIALECT: "mysql"
DB_HOST: "db"
DB_PORT: ${DB_PORT}
@ -85,7 +85,7 @@ services:
- internal
- edge
ports:
- ${SERVER_PORT:-3002}:${SERVER_PORT:-3002}
- ${API_PORT:-3002}:${API_PORT:-3002}
labels:
traefik.enable: "true"
# Router
@ -93,7 +93,7 @@ services:
traefik.http.routers.${COMPANY}-api.entrypoints: websecure
traefik.http.routers.${COMPANY}-api.tls.certresolver: cfresolver
# Servicio
traefik.http.services.${COMPANY}-api.loadbalancer.server.port: "${SERVER_PORT:-3002}"
traefik.http.services.${COMPANY}-api.loadbalancer.server.port: "${API_PORT:-3002}"
# --- Web estática (React compilado por build-factuges.sh) ---
web:

View File

@ -3,7 +3,7 @@ DOMAIN=rodax.factuges.rodax-software.local
FRONTEND_URL=rodax.factuges.rodax-software.local
WEB_VERSION=v0.0.4-latest
API_IMAGE=factuges-server:rodax-latest
SERVER_PORT=3002
API_PORT=3002
DB_HOST=db
DB_DIALECT=mysql
DB_PORT=3306

View File

@ -0,0 +1,52 @@
# Identidad de la compañía
COMPANY=rodax
# Dominios
DOMAIN=factuges.rodax-software.local
# MariaDB
DB_ROOT_PASS=verysecret
DB_USER=rodax_usr
DB_PASS=supersecret
DB_NAME=rodax_db
DB_PORT=3306
# API
API_PORT=3002
API_IMAGE=factuges-server:rodax-latest
TEMPLATES_PATH=/repo/apps/server/templates
FRONTEND_URL=factuges.rodax-software.local
# SYNC
ENV = development
LOCAL_TZ = Europe/Madrid
STATE_PATH = /app/state
SYNC_MODE = all
FACTUGES_HOST = factuges-pc.rodax-software.local
FACTUGES_PORT = 3050
FACTUGES_DATABASE = C:\FactuGES\FACTUGES.FDB
FACTUGES_USER = sysdba
FACTUGES_PASSWORD = masterkey
FWEB_MYSQL_HOST = db # ${DB_NAME}
FWEB_MYSQL_PORT = 3306 # ${DB_PORT}
FWEB_MYSQL_DATABASE = factuges_acana # ${DB_NAME}
FWEB_MYSQL_USER = acana # ${DB_USER}
FWEB_MYSQL_PASSWORD = r@U8%GJ+2e/AWR # ${DB_PASS}
CTE_COMPANY_ID = '5e4dc5b3-96b9-4968-9490-14bd032fec5f'
CTE_SERIE = 'F25'
CTE_STATUS_INVOICE = 'issued'
CTE_IS_PROFORMA = 0
CTE_STATUS_VERIFACTU = 'Pendiente'
CTE_LANGUAGE_CODE = 'es'
CTE_COUNTRY_CODE = 'es'
CTE_IS_COMPANY = 1
CTE_SYNC_RESULT_OK = 1
CTE_SYNC_RESULT_FAIL = 2
VERIFACTU_API_KEY = vf_prod_yfjonNPv2E4Fij+5J0hct0zCgUeFYT2dZzb23UZlM+Q=
VERIFACTU_BASE_URL = https://api.verifacti.com/
VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno=