尚未检测到更新。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).