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.
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.
| Dato | Valor |
|---|---|
| Nombre del paquete | rosver-sac |
| Versión actual | v0.1.49 |
| Repositorio | github.com/RosverSystem/Rosver-Web (rama main) |
| Producción | https://rosversac.com — hosteado en Railway |
| Frontend | React 19 + TypeScript + Vite + Tailwind CSS v4 |
| Backend | Hono (Node.js) + PostgreSQL + Redis (caché) + Cloudflare R2 (archivos) |
| Puertos en local | UI :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.
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)
Paso a paso
- El navegador (React 19 + React Router 7) hace
fetcha/api/...con cookies de sesión incluidas (credentials: "include"). - Hono recibe la petición: aplica CORS, logger, y en rutas protegidas el middleware
requireAuth+requireRole. - 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.
- El acceso a Postgres se hace con el driver
pg(pool de conexiones) — sin ORM; las queries son SQL explícito en cada archivo. - Home/destacados usan Redis como caché opcional — si no está configurado, se degrada a Postgres directo, nunca es un punto de falla dura.
- Imágenes, PDFs y evidencias se guardan en Cloudflare R2 (bucket público para media, privado para documentos/evidencias).
Stack tecnológico
Librerías principales, versión y para qué se usan.
Frontend
| Librería | Versión | Uso |
|---|---|---|
| react / react-dom | 19.x | UI — componentes funcionales + hooks |
| react-router-dom | 7.x | Ruteo SPA (público + /admin + /cuenta) |
| vite | 8.x | Bundler / dev server, HMR |
| tailwindcss | v4 | Utilidades CSS, tokens de marca en @theme |
| motion (Framer Motion) | 13.x | Animaciones de componentes React |
| gsap + @gsap/react | 3.x | Animaciones complejas (intro, scroll, gráficas) |
| cssvg-icons | 1.x | Único set de iconos permitido para UI nueva |
| zod | 4.x | Validación de formularios en el cliente |
| jspdf / @react-pdf/renderer / pdf-lib | — | Generación de PDFs de cotización/pedido/catálogo |
Backend
| Librería | Versión | Uso |
|---|---|---|
| hono | 4.x | Framework HTTP — rutas, middleware, CORS |
| pg | 8.x | Driver PostgreSQL (pool de conexiones, sin ORM) |
| argon2 | 0.45.x | Hash de contraseñas |
| otplib | 13.x | TOTP (2FA con autenticador) |
| ioredis | 5.x | Cliente Redis (caché opcional) |
| @aws-sdk/client-s3 | 3.x | Cliente S3-compatible para Cloudflare R2 |
| nodemailer | 10.x | Envío de correo (OTP, notificaciones) |
| puppeteer | 25.x | Render de PDF del catálogo completo (headless Chrome) |
| exceljs / xlsx | — | Importación de productos desde Excel |
| tsx | 4.x | Ejecuta 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).
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.
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
Auth & Seguridad
Catálogo
Productos, precios, marcas
12 tablas
Catálogo
Comercial
Pedidos, cotizaciones, combos
4 tablas
Comercial
Perú / Geo
Ubigeo para envíos
3 tablas
Perú / Geo
Analítica & Contenido
Demanda, vistas, mensajes
5 tablas
Analítica & Contenido
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 CONFLICTsiempre 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 enserver/scripts/. - Correr
npm run db:migratelocalmente antes de subir — el boot de producción corremigrate → seed → seed-ubigeo, en ese orden, en cada deploy.
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
| Mecanismo | Cómo está implementado |
|---|---|
| Contraseñas | Hasheadas con Argon2, nunca en texto plano ni en logs |
| Cookie de sesión | HttpOnly + SameSite=Lax + Secure en producción |
| 2FA | OTP por correo o TOTP con app autenticadora, gestionable en /cuenta/perfil |
| Login con Google | OAuth 2.0 con validación de state anti-CSRF |
| Rate limiting | Login/OTP: 5 intentos fallidos → bloqueo temporal por email+IP |
| Auditoría de accesos | Tabla 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".
Módulos del frontend
Las 15 features de src/features/.
| Feature | Ruta pública | Qué resuelve |
|---|---|---|
| auth | /login /registro /recuperar | Login, registro, recuperación, OTP/2FA, OAuth Google |
| catalog | /catalogo /producto/:slug /ofertas | Listado, filtros, ficha de producto, ranking de demanda |
| cart | /carrito | Carrito, promos 2×1 server-side, checkout de pedido |
| quotes | /cotizar /c/:slug | Solicitud de cotización, PDF, seguimiento público por link |
| account | /cuenta/* | Perfil, pedidos, cotizaciones, empresa, reseñas del cliente |
| contact | /contacto | Formulario de contacto + FAQ |
| complaints-book | /libro-de-reclamaciones | Libro de reclamaciones (regulación peruana) |
| admin-catalog | /admin/productos … | CRUD de marcas, categorías, productos, precios, specs, combos |
| admin-orders | /admin/pedidos | Pipeline de pedidos (confirmación → pago → envío → entregado) |
| admin-users | /admin/usuarios /admin/roles | Gestión de usuarios y matriz de roles/permisos |
| admin-clients | /admin/clientes | CRM liviano de clientes e interés por producto |
| admin-leads | /admin/leads | Mensajes de contacto capturados en la web pública |
| admin-content | /admin/contenido | Edición del hero y textos del home |
| admin-media | /admin/almacenamiento | Explorador de archivos en Cloudflare R2 |
| admin-analytics | /admin/analitica | Grá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.
API / Backend
Los 19 archivos de rutas en server/src/routes/.
| Archivo | Qué expone |
|---|---|
| auth.ts | Login, registro, OTP, 2FA, recuperación, OAuth Google |
| profile.ts | Perfil del usuario autenticado, avatar, reseñas propias |
| catalog.ts | Catálogo público: productos, filtros, búsqueda, destacados |
| orders.ts | Pedidos: creación, PDF, seguimiento público, /mine del cliente |
| quotes.ts | Cotizaciones: mismo patrón que pedidos |
| contact.ts / complaints.ts | Formulario de contacto y libro de reclamaciones |
| peru.ts | Ubigeo y autocompletado de direcciones |
| media.ts | Proxy de medios servidos desde R2 |
| admin.ts | Dashboard: KPIs generales del panel |
| admin-catalog.ts | CRUD completo de marcas/categorías/productos/precios/specs |
| admin-users.ts | Gestión de usuarios y roles (RBAC) |
| admin-clients.ts / admin-leads.ts | CRM de clientes y leads de contacto |
| admin-content.ts | Edición del contenido del home |
| admin-storage.ts | Explorador de R2 desde el panel |
| admin-analytics.ts | Series de demanda para las gráficas |
| admin-offer-combos.ts / admin-promos.ts | Combos 2×1/3×2 y promociones |
| admin-product-import.ts | Importació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.
Sistema de diseño
Paleta oficial, tipografía, iconos y medidas de banners/imágenes.
Paleta de colores (tokens obligatorios)
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
| Elemento | Medida recomendada |
|---|---|
| Logo en header/navbar | Alto objetivo 40–56 px en pantalla; SVG o PNG transparente recortado |
| Banner/hero de categoría o promo | Preferir CSS + SVG geométrico antes que una foto pesada full-bleed |
| Imagen de producto en grid | Cuadrada, 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 |
| Formato | WebP/AVIF para fotos; SVG para logos; nunca PNG/JPG sin comprimir en el critical path |
| CLS | Toda imagen declara width/height o aspect-ratio |
Responsive (obligatorio en toda pantalla nueva)
| Rango | Enfoque |
|---|---|
| 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 |
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.mdy03-vistas-y-flujos.mdpara entender el dominio antes de tocar una pantalla. - Revisar
docs/changes/— el últimoNNNN— para saber qué se hizo justo antes. - Feature nueva: usar la skill/checklist
create-featureantes de codear.
Al cerrar cualquier tarea
- Crear
docs/changes/NNNN-slug.mdcon qué cambió, por qué, cómo, archivos tocados y cómo verificarlo. - Si el cambio necesita persistencia nueva: migración
server/sql/NNN_*.sqlen 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.mdyRECOMENDACIONES.mdsi 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
Despliegue
Railway — web, Postgres, Redis y R2. GitHub como fuente de verdad.
Infraestructura
| Servicio | Rol |
|---|---|
| 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 R2 | Buckets de media pública y documentos privados (compatible S3) |
Línea de tiempo de un deploy
Push a GitHub main
El código cerrado y verificado localmente se sube a main.
railway up
Sube el código actual y dispara un build en Railway (Railpack detecta Node y corre npm run build).
boot.ts arranca el contenedor
Corre en orden: migrate.ts (aplica migraciones pendientes) → seed.ts (RBAC + admin) → seed-ubigeo.ts → arranca el servidor HTTP.
Healthcheck
Railway espera GET /api/health → 200 antes de enrutar tráfico al nuevo contenedor.
En línea
rosversac.com sirve la nueva versión; commitSha en /api/health confirma qué commit quedó desplegado.
Variables de entorno mínimas
| Variable | Para qué |
|---|---|
| DATABASE_URL | Conexió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_PASSWORD | Con qué credenciales se crea/actualiza el admin en cada boot |
| SMTP_HOST / SMTP_USER / SMTP_PASS | Envío de correo (OTP, notificaciones) |
| R2_ENDPOINT / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY | Almacenamiento de archivos |
| GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Login con Google (opcional) |
| REDIS_URL | Caché (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.
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.comhacia 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 auditperió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.
Glosario y comandos rápidos
Referencia de una página.
| Término | Significado en este proyecto |
|---|---|
| SystemRSV | Nombre del panel ERP interno (/admin) |
| RBAC | Control de acceso por rol — modules/permissions/roles/users |
| Combo (2×1, 3×2) | Promoción "compra N, paga M" calculada server-side |
| Ubigeo | Códigos oficiales de departamento/provincia/distrito de Perú |
| Feature | Un 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 fondodocs/architecture/03-vistas-y-flujos.md— cada pantalla y su flujodocs/pendientes/README.md— estado real y siempre vigente del proyectoCLAUDE.md/AGENTS.md— instrucciones para agentes de IA que trabajen en este repo
