Saltar al contenido principal

Backend

El backend vive en api/ y es un proyecto Node independiente ("type": "module", ESM).

Puntos de entrada​

ArchivoUso
api/src/app.jsCrea la app Express, registra middlewares y monta routers. No escucha puertos.
api/src/index.jsServidor local: app.listen(PORT), calienta el pool y resetea syncs colgados.
api/index.jsEntrada serverless para Vercel: export default app.

Pipeline de una petición​

cors (CORS_ORIGIN) → express.json({ limit: '2mb' }) → /uploads (estático, dev)
→ reset de sync colgado (solo 1ª petición del proceso)
→ router (/api/...)
→ requireAuth / optionalAuth → requireRole(...)
→ asyncHandler(handler) ← Zod.parse, queries, lib/*
→ notFound (404)
→ errorHandler

Montaje de rutas​

Todas las rutas cuelgan de /api (necesario para el rewrite de Vercel):

PrefijoRouterArchivo
/api/healthinlineapp.js
/api/authauthRouterroutes/auth.js
/api/productsproductsRouterroutes/products.js
/api/quotationsquotationsRouterroutes/quotations.js
/api/routesroutesRouterroutes/routes.js
/api/statsadminRouter.statsroutes/admin.js
/api/categoriesadminRouter.categoriesroutes/admin.js
/api/price-listsadminRouter.priceListsroutes/admin.js
/api/companiesadminRouter.companiesroutes/admin.js
/api/promotionsadminRouter.promotionsroutes/admin.js
/api/usersadminRouter.usersroutes/admin.js
/api/advisoradvisorRouterroutes/advisor.js
/api/my-companymyCompanyRouterroutes/myCompany.js
/api/integrations/siigosiigoRouterroutes/siigo.js

Middlewares​

Autenticación (middleware/auth.js)​

FunciónComportamiento
requireAuthExige Authorization: Bearer <token>. Sin token → 401 UNAUTHENTICATED; token inválido o vencido → 401 INVALID_TOKEN. Deja el payload en req.user.
optionalAuthSi hay token válido llena req.user; si no, sigue sin error. Se usa en endpoints de auto-registro.
requireRole(...roles)403 FORBIDDEN si req.user.role no está en la lista.
signTokenAccess token con { sub, role, email }, expira en JWT_EXPIRES_IN (8h por defecto).
signRefreshTokenRefresh token con { sub }, firmado con JWT_REFRESH_SECRET (o JWT_SECRET + '_refresh'), expira en JWT_REFRESH_EXPIRES_IN (30d).

Errores (middleware/error.js)​

  • ApiError(status, code, message, details?) es la excepción de negocio.
  • errorHandler traduce:
    • ZodError → 400 { error: 'VALIDATION_ERROR', message, details: err.flatten() }
    • ApiError → status { error: code, message, details? }
    • cualquier otro → 500 { error: 'INTERNAL' } y lo registra en consola.
  • notFound → 404 { error: 'NOT_FOUND', message: 'Route not found' }.

Uploads (middleware/upload.js)​

  • Multer con almacenamiento en memoria (compatible con serverless).
  • Campo del formulario: image. Un solo archivo, máximo 5 MB.
  • Tipos permitidos: JPEG, PNG, WebP, GIF.
  • Errores: FILE_TOO_LARGE, INVALID_MIME, UNEXPECTED_FILE (todos 400).
  • generateFilename crea un nombre aleatorio de 32 caracteres hex conservando la extensión.

asyncHandler (lib/validate.js)​

Envuelve handlers async para que cualquier excepción llegue a errorHandler sin try/catch manual.

Acceso a datos (config/db.js)​

  • pg.Pool con configuración según entorno:
EntornomaxidleTimeoutMillis
Serverless (NODE_ENV=production sin PORT)21 000
Servidor NodeDB_MAX_CONNECTIONS o 1030 000
  • Si existe DATABASE_URL se usa con SSL (rejectUnauthorized: false). Si no, se usan DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD sin SSL.
  • Exporta query(text, params) (loguea la duración en desarrollo) y getClient() para transacciones manuales (el llamador hace release()).
  • Los errores de clientes inactivos se registran pero no tumban el proceso.

Librerías de negocio (lib/)​

MóduloResponsabilidad
pricing.jsresolvePrices({ productIds, priceListId, clientId }) → precio base, de lista, de promoción y final por producto. Ver Reglas de negocio.
cutoff.jscomputeRouteCutoff(route) → ventana de corte en America/Bogota con Luxon.
mailer.jsCorreos transaccionales con Nodemailer. Ver Correos.
storage.jsSubida/borrado de imágenes en Supabase Storage vía S3. Ver Almacenamiento.
siigo/client.jsCliente HTTP de SIIGO con token cacheado en BD y reintento en 401.
siigo/mapper.jsProducto SIIGO → producto local.
siigo/customerMapper.jsCliente SIIGO ↔ empresa local.
siigo/quoteMapper.jsCotización interna → payload POST /v1/quotations.

Servicios (services/)​

MóduloResponsabilidad
siigoSync.jsSincronización de productos en segundo plano, logs y reseteo de syncs colgados.
siigoQuote.jsPush de cotizaciones a SIIGO con validaciones, idempotencia y reintentos.
siigoCustomer.jsPreview, importación (individual y masiva) y exportación de clientes.

Dependencias​

PaqueteUso
express, corsServidor HTTP
pgPostgreSQL
zodValidación de entradas
jsonwebtoken, bcryptjsAuth
luxonFechas con zona horaria (corte de rutas)
multerSubida de imágenes
@aws-sdk/client-s3Supabase Storage
nodemailerCorreos
dotenvVariables de entorno