尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Validar curaduria y encuentas sobre porfatolio
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Free Soul DNA · Plataforma de Operaciones Ecosistema web interno de Free Soul DNA (marca de importación D2C/B2B de ropa, calzado y accesorios). Arquitectura: Frontend en Vercel, Backend en Render, persistencia en Google Sheets (con respaldo local si Sheets falla).檢視完整 README ↗
Ecosistema web interno de Free Soul DNA (marca de importación D2C/B2B de ropa, calzado y accesorios). Arquitectura: Frontend en Vercel, Backend en Render, persistencia en Google Sheets (con respaldo local si Sheets falla).
Módulos:
freesoul-feedback/
├── server/ → API Node/Express (productos, encuestas, pedidos, Google Sheets)
└── client/ → App React + Tailwind (mobile-first)
El Admin puede crear dos tipos de dinámica, cada una con su propio link de evaluador:
/admin/results): tiene una pestaña por cada tipo de dinámica.
npm run install:all
Copia el archivo de ejemplo:
cp server/.env.example server/.env
Abre server/.env y define al menos:
ADMIN_PASSWORD=tu-clave-para-entrar-al-panel
JWT_SECRET=una-cadena-larga-y-aleatoria
Con esto la app ya funciona completo (guardando localmente). Google Sheets es opcional y se puede activar después sin tocar código.
npm run dev
Entra con la contraseña que pusiste en ADMIN_PASSWORD.
freesoul-feedback) y termina el asistente..json.server/credentials/service-account.json (esa carpeta ya existe y está en .gitignore, no se sube a git)."client_email" (algo como [email protected])./d/ y /edit:
https://docs.google.com/spreadsheets/d/ESTE_ES_EL_ID/editserver/.env completa:GOOGLE_SERVICE_ACCOUNT_JSON=./credentials/service-account.json
GOOGLE_SHEET_ID=ESTE_ES_EL_ID
GOOGLE_SHEET_TAB=Respuestas
Respuestas (o el nombre que pusiste en GOOGLE_SHEET_TAB).npm run dev). En la consola debe aparecer ✅ Google Sheets configurado.A partir de ahí, cada respuesta nueva se agrega automáticamente como fila con las columnas:
Evaluador | Fecha | Categoria | Producto | Atractivo | Calidad | Precio | Compraria | Comentarios | Encuesta_ID
Las respuestas de Curaduría de Portafolio se guardan en dos pestañas adicionales (se crean solas la primera vez que hay una respuesta):
Curaduria_Respuestas — una fila por evaluador: Evaluador_ID | Fecha | Encuesta_ID | Categoria | Productos_Seleccionados | Favorito_Top1 | ComentariosCuraduria_Ranking — se reescribe completa con los totales actualizados: Producto_ID | Producto | Encuesta_ID | Votos_Recibidos | %_Tasa_Seleccion | EtiquetaSi en algún momento Sheets falla (caída, permisos, cuota), las respuestas se siguen guardando localmente y puedes forzar el reenvío con el botón "Sincronizar con Sheets" en /admin/results.
No requiere tocar código: al crear un producto, elige "+ Nueva categoría..." en el formulario y escribe el nombre (ej. "Camisetas", "Bolsos"). Los criterios de evaluación son los mismos para todas las categorías, así que el dashboard de rankings las compara de forma consistente. Puedes filtrar los resultados por categoría en /admin/results.
| Criterio | Pregunta | Escala |
|---|---|---|
| Atractivo Visual | ¿Qué tan atractivo es el diseño? | 1-5 |
| Calidad Percibida | ¿Sientes que la calidad es buena? | 1-5 |
| Precio Justo | ¿El precio es justo para lo que ves? | 1-5 |
| Intención de Compra | ¿Lo comprarías? | Sí/No |
| Comentarios | ¿Qué cambiarías o qué te gusta? | Texto libre |
Puedes ajustar estos umbrales en server/src/services/scoring.js.
/admin/pedidos)Herramienta para construir órdenes de compra a partir de links de proveedor (1688/Alibaba) y calcular el costo puesto en Colombia mientras se digitan cantidades y costos.
Flujo:
URL_Producto, URL_Imagen, Referencia — una fila por imagen; si un producto tiene varias fotos, repite la misma URL de producto en varias filas y la app las agrupa sola. Acepta con o sin encabezado.Cantidad Total = Cantidad por Empaque × Cantidad de EmpaquesCosto Unitario USD = Costo Unitario RMB × Tasa RMB→USD (la tasa se configura por pedido, en "⚙️ Tasas de conversión y flete")Precio Total FOB (USD) = Costo Unitario USD × Cantidad TotalFlete Total (COP) = Flete por unidad (según categoría, o el valor manual que pongas en esa fila) × Cantidad TotalCosto Landed Total (COP) = Precio Total FOB × TRM (USD→COP) + Flete TotalGestion_Pedidos de tu Google Sheet (una fila por producto). Vuelve a sincronizar cuando edites algo — reemplaza solo las filas de ese pedido, no toca los demás.Importante — las fotos de este módulo NO viven en Google Sheets (Sheets no guarda imágenes, solo texto/números). Si el servidor de Render no tiene disco persistente contratado (ver sección de despliegue más abajo), las fotos descargadas —y los pedidos que no hayas sincronizado— se pueden perder en un reinicio del servicio. Sincroniza seguido y considera el disco persistente si vas a usar este módulo para pedidos reales.
La app soporta correr la misma suite completa para varias marcas desde un solo código — hoy Free Soul DNA y Alas de Amara ("Vuela a tu manera"), pensado para agregar más a futuro sin reescribir nada.
Cómo funciona:
server/src/brands.js): cada marca es una entrada con su propio archivo de datos local (db.<marca>.json), su propia hoja de Google Sheets (<PREFIJO>_GOOGLE_SHEET_ID en .env) y su propia contraseña de admin (<PREFIJO>_ADMIN_PASSWORD). El frontend manda qué marca es en el header X-Brand-Id en cada request; un middleware (server/src/middleware/brand.js) la deja disponible vía AsyncLocalStorage para que jsonStore.js y sheets.js resuelvan solos el archivo/spreadsheet correcto — ninguna ruta necesitó cambiar su código para volverse "consciente de marca". El token de sesión (JWT) lleva la marca adentro: una sesión de una marca no puede tocar datos de otra.client/src/brands.js + BrandContext.jsx): cada marca define nombre, slogan, logo, favicon y una paleta de colores (como variables CSS --brand-*/--sand-* que alimentan todas las clases Tailwind bg-brand-600, text-brand-700, etc. ya usadas en toda la app — rebrandear es cambiar esos valores, no tocar componentes). La marca se resuelve por el prefijo de la URL (/alas/admin, /alas/survey/:id, ...) mientras las marcas compartan dominio; Free Soul (sin prefijo) sigue funcionando exactamente en las mismas rutas de siempre.Para agregar una marca nueva:
BRANDS (server/src/brands.js) con sus nombres de variables de entorno, y define esas variables en .env (local) / Render (producción) — ver server/.env.example.BRANDS (client/src/brands.js) con su pathPrefix, textos, logo/favicon (ponlos en client/public/brands/<id>/) y su paleta de colores.Despliegue separado a futuro: hoy todas las marcas viven en el mismo dominio (mismo proyecto de Vercel/Render). Para independizar una marca con su propio dominio, se despliega el mismo código en un proyecto de Vercel + servicio de Render nuevos, con la variable VITE_BRAND=<id> fijada al buildear (ver client/.env.example) — esto fuerza esa marca sin necesitar ningún prefijo de ruta, y las URLs quedan limpias (/admin en vez de /alas/admin). Los datos y la hoja de Sheets de esa marca no se mueven (ya estaban separados desde el día uno).
La app ya está lista para desplegarse así: backend en Render, frontend en Vercel. Para la guía rápida (5 minutos), ver DEPLOY.md. Esta sección explica el detalle de cada paso y el porqué.
server, build npm install, start npm start, health check /api/health).ADMIN_PASSWORD — la clave del panel admin en producción (usa una distinta a la de desarrollo).CORS_ORIGIN — la dejas vacía por ahora, la completas en el paso 8.2 una vez tengas la URL de Vercel.GOOGLE_SHEET_ID, GOOGLE_SHEET_TAB y GOOGLE_SERVICE_ACCOUNT_JSON_BASE64 — opcionales, ver 8.3.JWT_SECRET se genera solo (generateValue: true en el blueprint)./var/data (plan starter, de pago, ~USD 7/mes). Sin esto, las fotos subidas y las respuestas guardadas localmente se borran en cada deploy o reinicio — Render usa filesystem efímero por defecto. Si por ahora solo quieres probar sin costo, puedes cambiar plan: starter a plan: free y quitar el bloque disk en render.yaml antes de desplegar, pero ten en cuenta que perderás fotos/respuestas locales cada vez que Render reinicie el servicio (rara vez, pero pasa). Con Google Sheets configurado, las respuestas igual quedan seguras ahí aunque el disco se borre — solo se perderían las fotos y lo que esté pendiente de sincronizar.https://freesoul-feedback-api.onrender.com) — la necesitas para el frontend.client (importante, si no Vercel intenta compilar la raíz del monorepo).VITE_API_URL = https://freesoul-feedback-api.onrender.com
(la URL real de tu backend en Render, del paso 8.1 — sin / al final).https://freesoul-feedback.vercel.app.CORS_ORIGIN con esa URL de Vercel (y tu dominio propio si ya lo conectaste, separados por coma) → guarda (Render redeploya solo).client/vercel.json ya incluye el rewrite necesario para que las rutas de React Router (/admin, /survey/:id, /curacion/:id, etc.) funcionen al refrescar o compartir un link directo.
En local usas un archivo service-account.json en disco — en Render no conviene subir ese archivo a git ni depender de que sobreviva un redeploy. En vez de eso, pega el JSON completo codificado en base64 en la variable GOOGLE_SERVICE_ACCOUNT_JSON_BASE64:
node -e "console.log(require('fs').readFileSync('server/credentials/service-account.json').toString('base64'))"
Copia esa salida (una sola línea larga) y pégala como valor de GOOGLE_SERVICE_ACCOUNT_JSON_BASE64 en Render. Completa también GOOGLE_SHEET_ID y GOOGLE_SHEET_TAB (ver sección 4 de este README para los pasos de creación del Service Account y de compartir el Sheet, son los mismos).
Render (planes free/starter) suspende el servicio tras ~15 minutos sin tráfico; la primera visita después de eso tarda ~30-50s en "despertar" (cold start). /api/health ya existe para esto. Dos formas de evitarlo:
GET a https://tu-backend.onrender.com/api/health cada 10 minutos. No es 100% garantizado por Render pero funciona bien en la práctica.starter de pago, Render no suspende el servicio por inactividad — no necesitas el cron.test.freesoulfashion.com.co.test, valor cname.vercel-dns.com.76.76.21.21.)freesoulfashion.com.co (GoDaddy, Namecheap, tu proveedor local, etc.) y crea ese registro.CORS_ORIGIN en Render agregando https://test.freesoulfashion.com.co a la lista (separado por coma del dominio de Vercel).ADMIN_PASSWORD en Render es distinta a la de desarrollo local.CORS_ORIGIN en Render incluye la URL final de Vercel (y tu dominio propio si aplica).