Guía única de convenciones, criterios de aceptación y reglas operativas para el portal de clientes de Winbit.
Si algo no está claro o hay más de una opción válida, el agente DEBE preguntar antes de actuar. Nunca asumir. Nunca tomar decisiones ambiguas sin confirmación explícita del usuario.
README.md— Setup, uso y troubleshooting. NO se edita sin autorización.AGENTS.md— Este archivo. Convenciones y reglas operativas.
- Framework: React 18 + Vite 5.
- Lenguaje: JavaScript (no TypeScript). Decisión pragmática; la arquitectura facilita migración futura.
- Estilos: Tailwind CSS 3 (config en
tailwind.config.js). Color primario:#65a7a5. Font: Montserrat.
Paleta de colores:
| Token | Valor | Uso |
|---|---|---|
primary |
#65a7a5 |
Acento principal, botones |
dark.bg |
#0B0F0E |
Fondo base |
dark.card |
rgba(20, 20, 20, 0.6) |
Fondo de cards |
text.primary |
#e8e8e8 |
Texto principal |
text.muted |
#888888 |
Texto secundario |
text.dim |
#555555 |
Texto terciario |
border.dark |
rgba(255, 255, 255, 0.08) |
Bordes default |
border.accent |
rgba(101, 167, 165, 0.3) |
Bordes activos/cards |
success |
#9dd4cb |
Estado completado |
warning |
#d4bf82 |
Estado pendiente |
error |
#d48080 |
Estado rechazado |
Fondo global (body): Radial gradient sutil con dos elipses verdes sobre #0B0F0E. Definido en src/index.css. NO usar bg-dark-bg en contenedores — el gradiente viene del body.
Clases CSS propias (definidas en src/index.css):
| Clase | Uso |
|---|---|
winbit-card |
Card estándar con glassmorphism |
winbit-card--highlight |
Card principal (más peso visual) |
winbit-card--compact |
Card compacta (menos padding) |
card-inner |
Contenedor interior dentro de cards |
section-subtitle |
Subtítulo verde de sección |
badge-completed |
Badge estado completado (verde) |
badge-pending |
Badge estado pendiente (amarillo) |
badge-rejected |
Badge estado rechazado (rojo) |
badge-cancelled |
Badge estado cancelado (gris) |
info-box |
Caja informativa (borde verde) |
btn-copy |
Botón copiar (con hover glow) |
nav-icon |
Icono de navegación (color default) |
nav-icon-active |
Icono de navegación (color activo) |
nav-link |
Link de navegación (underline animado) |
Reglas de diseño:
- Todas las cards usan hover con
translateY(-2px)+ green glow. - Bordes de cards:
rgba(101, 167, 165, 0.3)mínimo. - Botón primario: hover con elevación y glow verde.
- Tablas: filas alternas con tinte verde, dividers suaves.
- Gráfico: línea con glow SVG, gradiente visible.
- Routing: React Router DOM 6.
- Data fetching: React Query (
@tanstack/react-query) constaleTime: 5min. - Auth: Firebase Auth (Google Sign-In).
- i18n: react-i18next (español default, inglés).
- PWA: vite-plugin-pwa (auto-update, workbox).
- Emails: EmailJS (notificaciones de solicitudes).
- Charts: Recharts.
- Testing: Vitest + React Testing Library + jsdom.
- Linting: ESLint 8 (flat config) + Prettier.
- Deploy: Firebase Hosting.
winbit-app/
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI (lint, test, build)
├── src/
│ ├── components/
│ │ ├── ui/ # Button, Card, EmptyState, ErrorMessage, Input, Modal, Select, Spinner, Toast
│ │ ├── layout/ # Header, Footer, ProtectedRoute
│ │ ├── features/
│ │ │ ├── auth/ # AuthProvider, AuthContext
│ │ │ ├── dashboard/ # BalanceCard, KpiCard, LastUpdated
│ │ │ ├── requests/ # DepositForm, WithdrawalForm
│ │ │ └── wallets/ # WalletCard, WalletList
│ │ └── ErrorBoundary.jsx
│ ├── pages/ # DashboardPage, HistoryPage, LoginPage, OperatingPage, RequestsPage, UnauthorizedPage, WalletsPage
│ ├── hooks/ # useAuth, useInvestorData, useInvestorHistory, useWallets
│ ├── services/ # api.js (capa API), firebase.js (Firebase SDK)
│ ├── utils/ # formatCurrency, formatDate, formatName, formatPercentage, truncateAddress, uploadImage
│ ├── config/ # wallets.js
│ ├── lib/ # queryClient.js (React Query config)
│ ├── test/ # setup.js, utils.jsx
│ ├── i18n.js # Configuración i18next
│ ├── App.jsx # Rutas principales
│ ├── main.jsx # Entry point
│ └── index.css # Tailwind imports
├── public/ # Assets estáticos
├── .env # Variables dev (no versionado)
├── .env.production # Variables prod (no versionado)
├── .env.production.example # Template (versionado)
├── firebase.json # Firebase Hosting config
├── vite.config.js # Vite + PWA + Test config
├── tailwind.config.js # Tailwind config
├── eslint.config.js # ESLint flat config
├── .prettierrc.cjs # Prettier config
├── .husky/pre-push # Pre-push hook
├── package.json
└── AGENTS.md
.envy.env.productionno se versionan. Versionar solo.env.production.example.- Variables requeridas:
VITE_API_URL— URL del backend (http://localhost:3000en dev,https://admin.winbit.com.aren prod).VITE_FIREBASE_API_KEY,VITE_FIREBASE_AUTH_DOMAIN,VITE_FIREBASE_PROJECT_ID,VITE_FIREBASE_STORAGE_BUCKET,VITE_FIREBASE_MESSAGING_SENDER_ID,VITE_FIREBASE_APP_ID.VITE_EMAILJS_SERVICE_ID,VITE_EMAILJS_TEMPLATE_ID_WITHDRAWAL,VITE_EMAILJS_TEMPLATE_ID_DEPOSIT,VITE_EMAILJS_PUBLIC_KEY.
- Usuario hace Sign-In con Google (Firebase Auth popup, fallback a redirect).
AuthProvidervalida email contraGET /api/public/investor/:email.- Si no existe o está inactivo → logout automático + mensaje de error.
- Si está activo → acceso permitido, datos del inversor disponibles.
ProtectedRouteverifica auth. Si no autenticado → redirect a/login.
- Base URL desde
VITE_API_URL. - Funciones:
getInvestorData(email)— Datos del inversor + portfolio.getInvestorHistory(email)— Historial de movimientos.getWallets()— Billeteras de depósito.createInvestorRequest(data)— Crear solicitud de depósito/retiro.validateInvestor(email)— Validar si el inversor existe y está activo.
- Manejo de errores: 404 (no encontrado), 403 (inactivo), otros (error genérico).
useInvestorData— Datos del inversor (staleTime: 5min).useInvestorHistory— Historial.useWallets— Billeteras.
- Idiomas: Español (default), Inglés.
- Storage:
localStoragekeywinbit_language. - Namespace único:
translation. - Secciones:
app,common,errors,nav,auth,footer,dashboard,deposits,withdrawals,requests,history,operating,unauthorized. - Regla: toda string visible al usuario debe pasar por
t(). Nunca mostrar constantes crudas (ej:TRADING_FEEdebe mostrarse como "Comisión de Trading").
| Constante | Español | Inglés |
|---|---|---|
DEPOSIT |
Depósito | Deposit |
WITHDRAWAL |
Retiro | Withdrawal |
OPERATING_RESULT |
Resultado Operativo | Operating Result |
TRADING_FEE |
Comisión de Trading | Trading Fee |
TRADING_FEE_ADJUSTMENT |
Comisión de Trading - Ajuste | Trading Fee - Adjustment |
REFERRAL_COMMISSION |
Comisión por referido | Referral Commission |
| Ruta | Componente | Descripción |
|---|---|---|
/login |
LoginPage |
Login con Google. |
/ |
DashboardPage |
Balance, KPIs, gráfico TWR. |
/history |
HistoryPage |
Historial de movimientos con detalle. |
/operating |
OperatingPage |
Resultados operativos. |
/requests |
RequestsPage |
Solicitudes de depósito/retiro. |
/wallets |
WalletsPage |
Billeteras para depósitos. |
/unauthorized |
UnauthorizedPage |
Acceso denegado. |
- Componentes funcionales con hooks.
- Props desestructuradas.
- Tailwind CSS para estilos (no CSS modules ni styled-components).
- Responsive: mobile-first. Muchas páginas tienen vista mobile y desktop separadas.
translateMovement(): convierte constantes de eventos a texto legible según idioma.movementLabel(): agrega metadata contextual (porcentaje de fee, período, etc.).- Colores por tipo de movimiento:
- Depósitos: verde
- Retiros: rojo
- Resultados operativos: verde/rojo según signo
- Trading fees: azul
- Referral commissions: púrpura/violeta
- Runner: Vitest (config en
vite.config.js). - Environment: jsdom.
- Setup file:
src/test/setup.js(cleanup, i18n init, mocks). - Utilities:
src/test/utils.jsx.
- Archivos test junto al componente:
Component.test.jsx. - 66 archivos de test actualmente.
- Todos los componentes UI, pages, hooks y utils tienen tests.
- Mock de
fetch/API para controlar escenarios. - Mock de
navigator.clipboardywindow.matchMediaen setup. - Para elementos que aparecen después de fetch async: usar
findBy*(NOgetBy*). - Para queries dentro de un contenedor específico: usar
within(container).
npm run test # Watch mode
npm run test:ci # Single run (CI)
npm run test:coverage # Con cobertura- ESLint: Flat config (eslint.config.js). Plugins: react, react-hooks, react-refresh.
no-unused-varspermite prefijo_. - Prettier: Single quotes, semi, trailing comma all, print width 100, tab width 2.
- Pre-push hook:
npm run lint && npm run format:check && npm run test:ci. - Formato numérico argentino:
$XX.XXX,XX(función manual enutils/formatCurrency.js, NOtoLocaleString).
npm run lint # ESLint
npm run format:check # Prettier check
npm run format # Prettier fixnpm run build # Usa .env.production automáticamente- Output:
dist/. - PWA:
vite-plugin-pwaconminify: false(workaround para terser).
El deploy es automático: push a main → CI corre lint, test, build → deploy a Firebase Hosting.
También se puede deployar manualmente:
npm run build && firebase deployfirebase.json: hosting desdedist/, SPA rewrite a/index.html.- Predeploy:
npm run build(automático confirebase deploy). - Headers:
Cross-Origin-Opener-Policy: same-origin-allow-popups(requerido para Google Sign-In popup). - Producción: https://app.winbit.com.ar
Jobs:
lint— ESLint + Prettier check.test— Vitest (single run).build— Production build (depende de lint y test).
Se ejecuta en push a main y en pull requests.
Para cambios visuales menores (CSS, estilos, tamaños, colores, spacing), se permite pushear directo a main solo si todos los quality gates pasan.
Pasos obligatorios antes de pushear:
npm run lint # ESLint — debe pasar sin errores
npm run format:check # Prettier — debe pasar sin errores. Si falla, correr: npm run format
npm run test:ci # Vitest — todos los tests deben estar en verdeSi alguno falla: corregir el error y volver a correr los tres comandos. NUNCA pushear con errores.
Si todos pasan:
git add -A
git commit -m "Descripción breve del cambio"
git push origin mainDespués del push a main, el deploy a Firebase es automático (vía GitHub Actions). No hace falta correr nada manual.
Reglas:
- NUNCA pushear si algún quality gate falla.
- NUNCA commitear archivos
.envni secretos. - NUNCA mencionar IA, Cursor, Claude, Copilot ni herramientas similares en commits.
- Si el cambio toca lógica (JS, hooks, servicios), usar el flujo de PR en vez de push directo.
- Un branch por cambio puntual y atómico.
- Nombres de branch:
feature/<descripcion-corta>ofix/<descripcion-corta>. - Base siempre:
main.
- Mensaje conciso (1-2 líneas).
- NUNCA mencionar Cursor, Claude, AI, LLM, copilot ni herramientas de IA.
- PROHIBIDO cualquier trailer
Co-authored-byque mencione IA. - Usar
git -c commit.cleanup=verbatimpara evitar trailers automáticos.
git checkout main && git pull origin maingit checkout -b feature/<nombre>- Implementar cambio + tests
- Verificar:
npm run lint && npm run format:check && npm run test:ci git add(solo archivos relevantes, nunca secretos)git commit(mensaje conciso)git push -u origin feature/<nombre>gh pr create(título descriptivo, body con summary y test plan)- Esperar que CI pase (lint, test, build)
gh pr merge --squash --delete-branch- Volver a paso 1 para siguiente cambio
npm run lint(sin errores)npm run format:check(sin errores)npm run test:ci(todos en verde)- Si alguno falla, corregir antes de pushear.
El agente tiene autorización para: git add, commit, push, gh pr create, gh pr merge.
Condiciones:
- Cambios atómicos y trazables.
- Quality gates en verde (local + CI).
- Sin secretos versionados.
- No comandos destructivos salvo solicitud explícita.
- Event names internos: Siempre en inglés mayúsculas (
DEPOSIT,WITHDRAWAL, etc.). - Display al usuario: Siempre traducidos vía i18n. NUNCA mostrar constantes crudas.
- Status:
PENDING,COMPLETED,REJECTED. - Formato numérico: Argentino
$XX.XXX,XX(punto miles, coma decimales). - Fechas: Formato localizado según idioma del usuario.
- NO usar
toLocaleStringpara formato argentino (usar función manual). - NO mostrar constantes de eventos crudas al usuario (siempre traducir).
- NO commitear sin pasar lint, format y tests.
- NO crear archivos .md sin que el usuario lo pida.
- NO usar
getByRolepara elementos async en tests (usarfindByRole). - NO agregar TypeScript (decisión explícita de usar JS).
- NO usar CSS modules ni styled-components (solo Tailwind).
- NO modificar
.envni.env.production(solo.env.production.example).