Rosver
Documentación técnica · generada Setiembre 2026

Rosver SAC — el proyecto explicado de punta a punta

Catálogo web de importaciones + ERP interno SystemRSV. Esta guía existe para que cualquier desarrollador —nuevo en el equipo, estudiante o quien retome el proyecto— entienda cómo está armado el sistema completo: arquitectura, base de datos, seguridad, módulos y despliegue, sin depender de una conversación anterior.

rosversac.com — en producción React 19 · Hono · PostgreSQL v0.1.49 github.com/RosverSystem/Rosver-Web
0
Tablas en Postgres
0
Migraciones SQL
0
Módulos frontend
0
Archivos de rutas API
01 / 13

Resumen ejecutivo

Qué es Rosver SAC y para quién es esta guía.

Rosver SAC es una empresa peruana de importación y comercialización de herramientas y ferretería. Este repositorio contiene dos productos en una sola aplicación: una tienda web de catálogo B2B/B2C (importaciones, cotizaciones y pedidos) y un panel de administración interno llamado SystemRSV — un ERP liviano para gestionar catálogo, precios, pedidos, cotizaciones, clientes, contenido y analítica.

El proyecto ya no está en fase de mocks: catálogo, pedidos, cotizaciones, leads y reseñas corren contra PostgreSQL real en producción, no datos de ejemplo.

DatoValor
Nombre del paqueterosver-sac
Versión actualv0.1.49
Repositoriogithub.com/RosverSystem/Rosver-Web (rama main)
Producciónhttps://rosversac.com — hosteado en Railway
FrontendReact 19 + TypeScript + Vite + Tailwind CSS v4
BackendHono (Node.js) + PostgreSQL + Redis (caché) + Cloudflare R2 (archivos)
Puertos en localUI :5173 · API :8787
Regla de fase del producto: todo lo visual se diseña primero con mocks alineados al dominio, pero los flujos críticos (auth, catálogo, carrito, pedidos, cotizaciones, admin) ya corren contra datos reales — no quedan mocks en el camino de compra.
02 / 13

Arquitectura general

Cómo se conectan las piezas del sistema — y qué pasa exactamente cuando alguien hace clic.

La aplicación es un monorepo de un solo servicio: un proceso Node.js (Hono) sirve la API y, en producción, también sirve el build estático de React (SPA) desde el mismo origen. Esto simplifica CORS, cookies de sesión y el despliegue — un único servicio en Railway, sin balanceador ni gateway adicional.

El viaje de una petición (animado)

Cliente React 19 /cotizar, /admin… API Hono Zod valida requireAuth requireRole rate-limit Postgres pool (pg) 35 tablas 42 migraciones FKs + índices Redis / R2 caché home media/PDF
Cada punto rojo es una petición HTTP viajando por el pipeline real: validación → auth → base de datos → caché/archivos opcionales.

Paso a paso

  1. El navegador (React 19 + React Router 7) hace fetch a /api/... con cookies de sesión incluidas (credentials: "include").
  2. Hono recibe la petición: aplica CORS, logger, y en rutas protegidas el middleware requireAuth + requireRole.
  3. Los datos de entrada se validan con Zod antes de tocar la base de datos — nunca se confía en el body tal cual llega.
  4. El acceso a Postgres se hace con el driver pg (pool de conexiones) — sin ORM; las queries son SQL explícito en cada archivo.
  5. Home/destacados usan Redis como caché opcional — si no está configurado, se degrada a Postgres directo, nunca es un punto de falla dura.
  6. Imágenes, PDFs y evidencias se guardan en Cloudflare R2 (bucket público para media, privado para documentos/evidencias).
03 / 13

Stack tecnológico

Librerías principales, versión y para qué se usan.

Frontend

LibreríaVersiónUso
react / react-dom19.xUI — componentes funcionales + hooks
react-router-dom7.xRuteo SPA (público + /admin + /cuenta)
vite8.xBundler / dev server, HMR
tailwindcssv4Utilidades CSS, tokens de marca en @theme
motion (Framer Motion)13.xAnimaciones de componentes React
gsap + @gsap/react3.xAnimaciones complejas (intro, scroll, gráficas)
cssvg-icons1.xÚnico set de iconos permitido para UI nueva
zod4.xValidación de formularios en el cliente
jspdf / @react-pdf/renderer / pdf-libGeneración de PDFs de cotización/pedido/catálogo

Backend

LibreríaVersiónUso
hono4.xFramework HTTP — rutas, middleware, CORS
pg8.xDriver PostgreSQL (pool de conexiones, sin ORM)
argon20.45.xHash de contraseñas
otplib13.xTOTP (2FA con autenticador)
ioredis5.xCliente Redis (caché opcional)
@aws-sdk/client-s33.xCliente S3-compatible para Cloudflare R2
nodemailer10.xEnvío de correo (OTP, notificaciones)
puppeteer25.xRender de PDF del catálogo completo (headless Chrome)
exceljs / xlsxImportación de productos desde Excel
tsx4.xEjecuta TypeScript directo en Node (dev y prod)

Regla del proyecto: antes de instalar una librería nueva se revisa docs/architecture/04-stack-y-librerias.md — evita duplicar herramientas que ya existen (los iconos, por ejemplo, siempre van por cssvg-icons, nunca un paquete nuevo).

04 / 13

Estructura de carpetas

Dónde vive cada cosa en el monorepo.

Rosver-Web-1/
├── RosverSac/                 ← app (front + back)
│   ├── src/
│   │   ├── app/                shell, providers, layouts, rutas
│   │   ├── features/<nombre>/  un dominio de producto por carpeta
│   │   │     index.ts          ← único export público de la feature
│   │   │     ui/  model/  lib/
│   │   ├── shared/             UI/hooks/lib sin negocio de una feature
│   │   └── styles/             CSS global + tokens de marca
│   └── server/
│       ├── src/routes/         un archivo Hono por dominio de API
│       ├── src/lib/            lógica de negocio y helpers de servidor
│       ├── src/migrate.ts      corredor de migraciones (boot)
│       ├── src/seed.ts         RBAC + usuario admin/cliente demo
│       ├── sql/NNN_*.sql       migraciones numeradas, una por cambio
│       └── scripts/            scripts de operación (tests, imports, resets)
├── docs/                       toda la documentación viva del proyecto
│   ├── architecture/           stack, producto, paleta, despliegue
│   ├── changes/NNNN-slug.md    bitácora de cada cambio cerrado
│   ├── features/                ficha de cada feature
│   └── pendientes/             deudas y recomendaciones vivas
├── .claude/rules/  .cursor/rules/   reglas para agentes IA (Claude/Cursor)
└── scripts/railway-deploy.ps1  script de despliegue a Railway

Regla de dependencias entre capas

app/         →  puede importar  features/*  y  shared/
features/A   →  puede importar  shared/  (nunca internos de features/B)
shared/      →  NO puede importar  features/*

El alias @ apunta a RosverSac/src (import { LoginPage } from '@/features/auth'). Cada feature expone únicamente lo que declara en su index.ts — importar un archivo interno de otra feature está prohibido por convención del proyecto.

05 / 13

Base de datos

PostgreSQL — 35 tablas en 5 dominios, 42 migraciones. Sin ORM: SQL explícito.

Cada migración es un archivo SQL plano en server/sql/NNN_slug.sql, numerado secuencialmente y pensado para ser idempotente (IF NOT EXISTS, ON CONFLICT) cuando es posible. server/src/migrate.ts las corre en orden en cada arranque del servidor — así el esquema nunca queda desincronizado del código.

Los 5 dominios (clic para expandir)

Auth & Seguridad

Quién entra, con qué rol
11 tablas
roles code, name role_permissions role_id, permission_id permissions module, action users role_id, email, hash sessions user_id, expires auth_otps code_hash oauth_accounts google id
modules5 col
roles7 col
permissions6 col
role_permissions2 col
users17 col
sessions9 col
auth_otps9 col
oauth_accounts6 col
login_challenges7 col
login_audit7 col
pending_registrations7 col

Catálogo

Productos, precios, marcas
12 tablas
brands slug, logo_url categories parent_id ↺ products sku, code, slug product_prices valid_from/to price_audit changed_by product_packagings unit_type_id product_spec_values attribute_id
brands11 col
categories16 col
unit_types7 col
unit_type_quantities7 col
products24 col
product_packagings8 col
product_prices17 col
price_audit10 col
spec_attributes11 col
product_spec_values7 col
product_reviews10 col
product_promos12 col

Comercial

Pedidos, cotizaciones, combos
4 tablas
order_requests items jsonb quote_requests public_slug offer_combos buy_qty / pay_qty offer_combo_items combo_id, product_id ship_district_code → peru_districts user_id → users (nullable, checkout invitado)
order_requests33 col
quote_requests30 col
offer_combos18 col
offer_combo_items7 col

Perú / Geo

Ubigeo para envíos
3 tablas
peru_departments 25 filas · CHAR(2) peru_provinces 196 filas · CHAR(4) peru_districts 1873 filas · CHAR(6)
peru_departments3 col
peru_provinces4 col
peru_districts5 col

Analítica & Contenido

Demanda, vistas, mensajes
5 tablas
user_product_views product_id, viewed_at product_metrics_daily day, views, sales site_content hero / textos contact_messages leads /admin consumer_complaints libro reclamaciones
product_metrics_daily5 col
user_product_views5 col
site_content3 col
contact_messages15 col
consumer_complaints28 col

Convención para migraciones nuevas

  • Un archivo nuevo por cambio: server/sql/043_slug_descriptivo.sql — nunca editar una migración ya desplegada.
  • Usar IF NOT EXISTS / ON CONFLICT siempre que se pueda, para que correr la migración dos veces no falle.
  • Si el cambio toca datos que ya existen en producción, documentarlo en docs/changes/ y, si aplica, escribir un script en server/scripts/.
  • Correr npm run db:migrate localmente antes de subir — el boot de producción corre migrate → seed → seed-ubigeo, en ese orden, en cada deploy.
06 / 13

Autenticación, roles y seguridad

RBAC, sesiones, contraseñas y hallazgos de auditoría corregidos.

El sistema usa RBAC (control de acceso por rol) con dos roles de sistema —admin y client— respaldados por una matriz real en base de datos: modules → permissions → role_permissions → roles → users.

Sesión y credenciales

MecanismoCómo está implementado
ContraseñasHasheadas con Argon2, nunca en texto plano ni en logs
Cookie de sesiónHttpOnly + SameSite=Lax + Secure en producción
2FAOTP por correo o TOTP con app autenticadora, gestionable en /cuenta/perfil
Login con GoogleOAuth 2.0 con validación de state anti-CSRF
Rate limitingLogin/OTP: 5 intentos fallidos → bloqueo temporal por email+IP
Auditoría de accesosTabla login_audit + panel en /admin/usuarios

Rutas de administración

Las 10 familias de rutas bajo /api/admin/* aplican requireAuth + requireRole('admin') a nivel de router — no hay endpoints admin "huérfanos" sin ese guardia. Verificado archivo por archivo en la auditoría técnica del proyecto.

Hallazgos de la auditoría técnica y su corrección

IDOR en subida de PDF

PUT /api/orders/:id/pdf y /api/quotes/:id/pdf permitían sobrescribir el PDF de un pedido ajeno. Corregido: verificación de magic bytes + regla "una sola subida" (409 si ya existe).

Cortina de intro colgada

IntroTransition.tsx podía quedar bloqueando toda la UI si el timeline de animación no completaba. Corregido con un tope de seguridad de 4 segundos.

Para quien continúe el proyecto: antes de dar por buena una funcionalidad de seguridad, probarla con una petición HTTP real — no basta con que el código "se vea correcto".
07 / 13

Módulos del frontend

Las 15 features de src/features/.

FeatureRuta públicaQué resuelve
auth/login /registro /recuperarLogin, registro, recuperación, OTP/2FA, OAuth Google
catalog/catalogo /producto/:slug /ofertasListado, filtros, ficha de producto, ranking de demanda
cart/carritoCarrito, promos 2×1 server-side, checkout de pedido
quotes/cotizar /c/:slugSolicitud de cotización, PDF, seguimiento público por link
account/cuenta/*Perfil, pedidos, cotizaciones, empresa, reseñas del cliente
contact/contactoFormulario de contacto + FAQ
complaints-book/libro-de-reclamacionesLibro de reclamaciones (regulación peruana)
admin-catalog/admin/productos …CRUD de marcas, categorías, productos, precios, specs, combos
admin-orders/admin/pedidosPipeline de pedidos (confirmación → pago → envío → entregado)
admin-users/admin/usuarios /admin/rolesGestión de usuarios y matriz de roles/permisos
admin-clients/admin/clientesCRM liviano de clientes e interés por producto
admin-leads/admin/leadsMensajes de contacto capturados en la web pública
admin-content/admin/contenidoEdición del hero y textos del home
admin-media/admin/almacenamientoExplorador de archivos en Cloudflare R2
admin-analytics/admin/analiticaGráficas de demanda por producto/categoría

Cada feature sigue la misma anatomía: index.ts (export público), ui/ (páginas), model/ (estado/hooks), y opcionalmente api/ y lib/. Para crear una feature nueva existe la skill create-feature, que arma las carpetas, la ficha en docs/features/ y el registro del cambio.

08 / 13

API / Backend

Los 19 archivos de rutas en server/src/routes/.

ArchivoQué expone
auth.tsLogin, registro, OTP, 2FA, recuperación, OAuth Google
profile.tsPerfil del usuario autenticado, avatar, reseñas propias
catalog.tsCatálogo público: productos, filtros, búsqueda, destacados
orders.tsPedidos: creación, PDF, seguimiento público, /mine del cliente
quotes.tsCotizaciones: mismo patrón que pedidos
contact.ts / complaints.tsFormulario de contacto y libro de reclamaciones
peru.tsUbigeo y autocompletado de direcciones
media.tsProxy de medios servidos desde R2
admin.tsDashboard: KPIs generales del panel
admin-catalog.tsCRUD completo de marcas/categorías/productos/precios/specs
admin-users.tsGestión de usuarios y roles (RBAC)
admin-clients.ts / admin-leads.tsCRM de clientes y leads de contacto
admin-content.tsEdición del contenido del home
admin-storage.tsExplorador de R2 desde el panel
admin-analytics.tsSeries de demanda para las gráficas
admin-offer-combos.ts / admin-promos.tsCombos 2×1/3×2 y promociones
admin-product-import.tsImportación masiva de productos desde Excel

Convención de una ruta admin nueva

export const adminXxxRoutes = new Hono<{ Variables: AuthVariables }>()
adminXxxRoutes.use('*', requireAuth, requireRole('admin'))

adminXxxRoutes.get('/', async (c) => { /* SELECT ... */ })
adminXxxRoutes.post('/', async (c) => {
  const body = createSchema.safeParse(await c.req.json().catch(() => null))
  if (!body.success) return c.json({ error: body.error.issues[0]?.message }, 400)
  // INSERT ...
})

Regla CRUD completo: al tocar una tabla o módulo de datos nuevo, se espera Create + Read + Update + Delete (o soft-delete) tanto en la API como en la pantalla de /admin correspondiente.

09 / 13

Sistema de diseño

Paleta oficial, tipografía, iconos y medidas de banners/imágenes.

Paleta de colores (tokens obligatorios)

rosver-red
#E30613
rosver-red-dark
#90040D
rosver-ink
#0D0D0D
rosver-blue
#1E3A5F
rosver-muted
#6B7280
rosver-line
#E5E7EB
rosver-soft
#F3F4F6
rosver-yellow
#F2B705
rosver-success
#10B981

Prohibido: hardcodear hex de marca en componentes o usar paletas genéricas — todo color pasa por estos tokens en RosverSac/src/styles/global.css.

Iconos y formularios

  • Iconos de UI nuevos: únicamente cssvg-icons (catálogo en icon.cssvg.com) — no se agregan librerías de iconos nuevas.
  • Formularios: toasts flotantes tipados (success/error/info/warning) vía FloatingToasts + useFormToasts — nunca alertas nativas del navegador.

Medidas de banners e imágenes

ElementoMedida recomendada
Logo en header/navbarAlto objetivo 40–56 px en pantalla; SVG o PNG transparente recortado
Banner/hero de categoría o promoPreferir CSS + SVG geométrico antes que una foto pesada full-bleed
Imagen de producto en gridCuadrada, servida al tamaño real de despliegue (no 2000 px para una miniatura de 200 px)
Imagen crítica (hero/LCP)Una sola por página con loading="eager" + fetchPriority="high"; el resto lazy
FormatoWebP/AVIF para fotos; SVG para logos; nunca PNG/JPG sin comprimir en el critical path
CLSToda imagen declara width/height o aspect-ratio

Responsive (obligatorio en toda pantalla nueva)

RangoEnfoque
Móvil (< 768 px)Una columna, menús colapsables, targets táctiles ≥ 44 px
Tablet (768–1023 px)Layout intermedio — ni "desktop encogido" ni "móvil estirado"
Desktop (≥ 1024 px)Densidad y hover; layouts fluidos hasta ultrawide
10 / 13

Cómo seguir desarrollando

El flujo que sigue el proyecto para cada cambio.

Antes de escribir código

  • Leer docs/architecture/02-producto-rosver-sac.md y 03-vistas-y-flujos.md para entender el dominio antes de tocar una pantalla.
  • Revisar docs/changes/ — el último NNNN — para saber qué se hizo justo antes.
  • Feature nueva: usar la skill/checklist create-feature antes de codear.

Al cerrar cualquier tarea

  • Crear docs/changes/NNNN-slug.md con qué cambió, por qué, cómo, archivos tocados y cómo verificarlo.
  • Si el cambio necesita persistencia nueva: migración server/sql/NNN_*.sql en el mismo trabajo.
  • Si se tocó una tabla/módulo de datos: verificar que el CRUD esté completo (Create+Read+Update+Delete) en API y en /admin.
  • Actualizar docs/pendientes/PENDIENTES.md y RECOMENDACIONES.md si quedó algo a medias.

Comandos de verificación

cd RosverSac
npx tsc -b                 # typecheck frontend
npm run typecheck:server   # typecheck backend
npm run lint                # oxlint
npm run build                # build de producción
npm run db:migrate           # aplica migraciones nuevas
npm run test:security        # tests de reglas de precio/seguridad
npm run test:oauth-state     # tests de OAuth state anti-CSRF
npm run test:promo-calc      # tests de cálculo de combos 2x1/3x2
11 / 13

Despliegue

Railway — web, Postgres, Redis y R2. GitHub como fuente de verdad.

Infraestructura

ServicioRol
Rosver-Web (Railway)Un solo proceso Node: sirve /api/* y el build estático de React
Postgres (Railway)Base de datos principal
Redis (Railway)Caché opcional de home/destacados — degrada solo a Postgres si no está
Cloudflare R2Buckets de media pública y documentos privados (compatible S3)

Línea de tiempo de un deploy

1

Push a GitHub main

El código cerrado y verificado localmente se sube a main.

2

railway up

Sube el código actual y dispara un build en Railway (Railpack detecta Node y corre npm run build).

3

boot.ts arranca el contenedor

Corre en orden: migrate.ts (aplica migraciones pendientes) → seed.ts (RBAC + admin) → seed-ubigeo.ts → arranca el servidor HTTP.

4

Healthcheck

Railway espera GET /api/health200 antes de enrutar tráfico al nuevo contenedor.

5

En línea

rosversac.com sirve la nueva versión; commitSha en /api/health confirma qué commit quedó desplegado.

Variables de entorno mínimas

VariablePara qué
DATABASE_URLConexión a Postgres (Railway la inyecta automático si está en el mismo proyecto)
APP_URL / CORS_ORIGIN(S)Dominio público — define orígenes CORS permitidos y la cookie
SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORDCon qué credenciales se crea/actualiza el admin en cada boot
SMTP_HOST / SMTP_USER / SMTP_PASSEnvío de correo (OTP, notificaciones)
R2_ENDPOINT / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEYAlmacenamiento de archivos
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETLogin con Google (opcional)
REDIS_URLCaché (opcional — el sistema funciona sin ella)

Comandos

# Desde RosverSac/, con RAILWAY_API_TOKEN en .env
railway up --detach          # sube el código actual y despliega
railway variables --set KEY=value   # configura variables de entorno
railway logs --build <id>    # ver el log de un build

# Verificación post-deploy
curl https://rosversac.com/api/health   # debe responder { ok: true, commitSha: ... }
Regla de oro: todo cambio cerrado debe terminar con push a GitHub main y deploy a Railway probado en línea — nunca dejar trabajo importante solo en local.
12 / 13

Estado actual y próximos pasos

Qué queda para quien continúe. El detalle siempre vigente vive en docs/pendientes/README.md.

Bloqueado por acción humana (no es deuda de código)

  • Credenciales de Google Cloud OAuth para habilitar "Continuar con Google" en producción.
  • Cutover de DNS de rosversac.com hacia el dominio custom de Railway (Cloudflare).

Recomendado para las próximas sesiones

  • Revisar el code-splitting de quotes/complaints-book — hoy se importan estático y dinámico a la vez y el chunk no se separa (bundle inicial ~634 KB).
  • Validar el tipo real de archivo (magic bytes) al subir avatar y evidencias de pedido, no solo el MIME declarado por el navegador.
  • Ejecutar npm audit periódicamente — xlsx (importador de Excel) tiene una vulnerabilidad conocida sin parche; su uso está limitado a /admin.
  • Sumar tests E2E (Playwright ya está instalado — npm run test:e2e) para checkout y panel admin.
13 / 13

Glosario y comandos rápidos

Referencia de una página.

TérminoSignificado en este proyecto
SystemRSVNombre del panel ERP interno (/admin)
RBACControl de acceso por rol — modules/permissions/roles/users
Combo (2×1, 3×2)Promoción "compra N, paga M" calculada server-side
UbigeoCódigos oficiales de departamento/provincia/distrito de Perú
FeatureUn dominio de producto en src/features/<nombre>/
Change (docs/changes/)Registro de un cambio cerrado — puente de contexto entre sesiones

Comandos del día a día

cd RosverSac
npm run dev        # UI en :5173 (Vite)
npm run dev:api     # API en :8787 (tsx watch)
npm run db:setup    # migrate + seed + seed-ubigeo, todo de una
npm run build        # build de producción
npm start             # build + migrate + seed + servidor único (como Railway)

Dónde seguir leyendo

  • docs/architecture/02-producto-rosver-sac.md — el producto explicado a fondo
  • docs/architecture/03-vistas-y-flujos.md — cada pantalla y su flujo
  • docs/pendientes/README.md — estado real y siempre vigente del proyecto
  • CLAUDE.md / AGENTS.md — instrucciones para agentes de IA que trabajen en este repo