Ecommerce Template
Next.js·React 등으로 만든 최신 전자상거래 템플릿
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Este es el documento de referencia único del proyecto. Reemplaza y consolida todos los README y LEEME anteriores. Está escrito para que otra persona —o una IA en una sesión nueva— retome el trabajo sin contexto previo. Estado: v16 · 19 de agosto de 2026 · en uso operativo real.README 전문 보기 ↗
Este es el documento de referencia único del proyecto. Reemplaza y consolida todos los README y LEEME anteriores. Está escrito para que otra persona —o una IA en una sesión nueva— retome el trabajo sin contexto previo.
Estado: v16 · 19 de agosto de 2026 · en uso operativo real.
Cómo se versiona la documentación (importante): Este README maestro se mantiene siempre al día con el estado actual del sistema. Cada entrega nueva se documenta además en su propio archivo de cambios
docs/CHANGELOG-Vx.md(V9, V10, …), que describe solo lo que cambió en esa versión y depende de este documento para el contexto. Así este README no se satura y el detalle histórico queda en los changelog. Ver la sección 12.
Si vas a retomar el trabajo, empieza por la sección 10 (lo que falta) y la sección 11 (bugs ya corregidos): es lo que más tiempo ahorra. Para desplegar, la sección 9.
Punto de Venta a medida para la tienda Sevelin (Arica, Chile), un negocio de venta y reparación de equipos electrónicos. Nació como práctica profesional de un Técnico de Nivel Superior en Informática y está en uso operativo real.
Cubre: ventas en caja, inventario con costos FIFO, finanzas y balance por canales de dinero, arqueo de caja, servicio técnico (órdenes de trabajo), abonos, repuestos de taller, gastos, mermas, reportes de negocio y control de acceso por roles.
Existe una app Android complementaria (Sevelin Print) que corre en la máquina de pago TUU Pro 2 y consume esta misma API.
No usa React ni Vue. Es JavaScript vanilla: cada módulo es un
.jsplano cargado con<script src>, y todos comparten el ámbito global. Esto tiene una consecuencia crítica de mantenimiento — ver la regla de oro en 2.2.
| Capa | Tecnología |
|---|---|
| Frontend | HTML5 + JavaScript vanilla |
| Estilos | CSS propio (styles.css) + Tailwind compilado (no CDN) |
| Backend | Node.js + Express, serverless en Vercel |
| Autenticación | JWT, token en sessionStorage |
| Base de datos | Supabase (PostgreSQL), acceso solo desde el backend |
| Reportes | SheetJS (Excel), jsPDF + AutoTable (PDF) |
css/tailwind.css es un archivo generado, no viene de CDN. Si agregas clases de Tailwind, recompila:
npm run css # compila una vez, minificado
npm run css:watch # recompila al guardar
Si no recompilas, la clase no existe en el archivo y no se ve. El orden de las hojas importa:
styles.css va antes que tailwind.css; invertirlos cambia estilos.
Como todos los .js comparten el ámbito global, dos archivos no pueden declarar una función con el
mismo nombre: la que carga después pisa a la otra en silencio, sin error en consola, y rompe algo
en un módulo lejano. Esto ya causó tres bugs graves (ver 11). Y el problema inverso también existe:
usar una función que no está definida en el frontend lanza ReferenceError y aborta el flujo (el
bug de los botones de Finanzas en la v9 fue exactamente esto: num() solo existía en el backend).
Antes de declarar o usar una función global, verifícalo:
# ¿alguien ya declaró esta función? (debe salir a lo sumo una vez)
grep -rn "function nombreDeLaFuncion" js/
# CHEQUEO OBLIGATORIO tras tocar varios archivos: funciones duplicadas
# (este patrón captura también las funciones indentadas y las async —
# el patrón viejo con '^(async )?function' se saltaba las indentadas y
# dejó pasar la colisión de confirmarEntrega, ver sección 11)
for f in js/*.js; do grep -oP '^\s*(async\s+)?function\s+\K[A-Za-z_$][\w$]*' "$f"; done \
| sort | uniq -d
# ↑ debe salir VACÍO. Si sale algo, hay una función pisando a otra.
Los helpers globales canónicos viven en js/config.js (carga primero): fmtCLP, escHtml, num,
fechas de Chile, toast. Reutilízalos, no los redefinas.
Navegador (vanilla JS) App Android (Sevelin Print)
│ │
│ fetch + JWT en Authorization
▼ ▼
Backend Express (api/index.js, serverless en Vercel)
│ llave secreta desde variables de entorno
▼
Supabase / PostgreSQL
Reglas que hay que respetar:
js/api.js es el único punto de contacto con el backend.plpgsql, no en JavaScript.sevelin-pos/
├── index.html ← TODA la interfaz (vistas + modales)
├── tailwind.config.js
├── package.json
├── vercel.json ← rewrite /api/(.*) → /api/index + cabeceras de seguridad
├── .env.example ← plantilla. El .env real NO se versiona ni se comparte
│
├── css/
│ ├── styles.css ← CSS propio (secciones numeradas)
│ ├── tailwind.css ← COMPILADO. Se sube al repo
│ └── tailwind-input.css ← Fuente. NO se enlaza
│
├── js/
│ ├── api.js ← Cliente HTTP único
│ ├── config.js ← Helpers globales: fmtCLP, escHtml, num, fechas Chile, toast
│ ├── auth.js · pinpad.js ← Login por PIN (campo de texto, admite letras), roles
│ ├── pos.js · pos-layout.js ← Carrito, venta, y el redimensionado del POS
│ ├── pago.js ← Medios de pago, vuelto, pago mixto, paso de DTE
│ ├── balance.js ← Finanzas: balance, gastos fijos, inyecciones, arqueo,
│ │ widget de saldos por canal, traspasos, resguardo
│ ├── finanzas-gate.js ← PIN obligatorio al entrar a Finanzas (req. seguridad)
│ ├── finanzas-ajustes.js ← Ajuste manual de saldos, checklist de fijos, aportes
│ ├── productos.js · lotes.js · tiendanube.js
│ ├── historial.js · compras.js · mermas.js
│ ├── repuestos.js · ot.js · encargos.js
│ ├── reportes.js · escaner.js · etiquetas.js · print.js · atajos.js
│ └── vendor/qrcode.min.js
│
├── api/index.js ← TODO el backend
│
├── sql/ ← Migraciones, EN ORDEN (01 … 18)
│
└── docs/ ← documentación (empezar por README-DOCS.md)
├── README-DOCS.md ← índice: qué leer según lo que necesitas
├── SNAPSHOT.md ← foto del estado actual (punto de retomada)
├── README.md ← el maestro (este archivo)
├── README-DESPLIEGUE.md ← guía de despliegue detallada
├── AUDITORIA-SEGURIDAD-SEVELIN-POS.md
├── CHANGELOG-V10.md … CHANGELOG-V16.md ← changelogs recientes
└── archivo/ ← histórico: READMEs viejos y CHANGELOG-V5…V9
api/index.jsdebe llamarse exactamente así y estar enapi/. Si queda en la raíz, Vercel lo sirve como estático y todas las rutas dan "Endpoint no encontrado". En Windows con extensiones ocultas, vigila que no quede comoindex.js.js.
config.js va primero (define los helpers globales que todos usan), luego api.js, auth.js, y
el resto. pos-layout.js va después de pos.js.
| Acción | Admin | Trabajador |
|---|---|---|
| Registrar ventas e imprimir tickets | ✅ | ✅ |
| Ver historial y reimprimir | ✅ | ✅ |
| Ver costos, utilidades y KPIs de ganancia | ✅ | ❌ (el servidor ni los envía) |
| Módulo Productos y Finanzas | ✅ | ❌ oculto y bloqueado |
| Crear / editar / eliminar productos | ✅ | ❌ 403 |
| Editar o eliminar ventas | ✅ | ❌ 403 |
| Cambiar el tipo de DTE de una venta | ✅ | ❌ 403 (y queda auditado) |
| Mover fondos entre canales (traspaso) | ✅ | ❌ |
El bloqueo no es solo visual: los endpoints devuelven 403 y las respuestas para trabajador salen
sin costo_total, utilidad ni costo_unitario.
Carrito, búsqueda por nombre/SKU/código, escáner, modo edición, dividir venta, pago mixto, y el paso
de selección de DTE. El área es redimensionable: se arrastra la esquina y las dos tarjetas
(Ingresar producto / Carrito) crecen juntas; el tamaño se guarda por equipo en localStorage.
Acceso protegido: al entrar a Finanzas se pide el PIN de administrador cada vez (aunque la
sesión esté abierta), y se vuelve a pedir al salir y regresar. Lo maneja js/finanzas-gate.js,
validando contra POST /api/verificar-pin.
Sub-pestañas, en orden: Historial de Ventas (por defecto) · Balance · Gastos · Gastos Fijos.
Widget de 4 tarjetas (Efectivo · Banco · Total · Resguardo). Efectivo y Banco tienen un lápiz que abre un modal de ajuste manual con justificación obligatoria e historial (los ajustes guardan un delta, no reescriben el saldo). El Total no se edita: se calcula. La tarjeta Resguardo muestra la suma de gastos fijos pendientes del mes y abre un checklist con el cuadre saldo-vs-pendiente. Botones: traspaso interno y gestión de aportes de capital (historial, agregar, borrar con aviso de efecto en el saldo). En el Balance, el gasto en mercadería se resalta como bloque destacado.
Widget de saldos por canal (vive dentro del panel Balance): calcula en tiempo real cuánto hay en
Efectivo (caja chica) y en Banco (cuentas), más el total. El canal de cada movimiento lo
deriva el backend del método de pago (esEfectivo()), sin una columna redundante que pueda
desincronizarse. Se refresca tras cada venta/gasto/OT vía el evento pos:movimiento-dinero.
Órdenes de trabajo con Check-In (recepción) y Check-Out (entrega con firma). El número de OT
(OT-000001) lo asigna un trigger de la base, no la app, para que dos check-in simultáneos no
choquen. Incluye repuestos por OT, notas y abonos.
Catálogo con paginación, importación desde Tiendanube, alertas de bajo stock, y costos FIFO por lotes (capas de costo resueltas en la base). Caché de 90 s que se invalida en toda mutación.
Se hizo una auditoría ofensiva completa. Todas las prioridades están cerradas. Resumen:
| Área | Estado |
|---|---|
| Credenciales rotadas (service_role, JWT, PINs) + RLS activa | ✅ |
Escape de HTML en todo dato de usuario (escHtml) — anti-XSS | ✅ |
| Precio/costo negativo y stock insuficiente rechazados en el servidor | ✅ |
| Documentos de compra en bucket privado con URLs firmadas | ✅ |
| CSP en el front (vercel.json) y en la API (helmet) | ✅ |
Cambio de DTE solo admin + tabla de auditoría (auditoria_dte) | ✅ |
CORS sin fallback a *, PINs de fábrica rechazados al arrancar | ✅ |
Detalle completo en docs/AUDITORIA-SEGURIDAD-SEVELIN-POS.md.
Reglas de seguridad permanentes:
.env real nunca se sube a git ni se comparte por chat. Solo existe .env.example.innerHTML pasa por escHtml.22 tablas en el esquema public. Las principales: productos, ventas, venta_items,
producto_lotes (FIFO), compras (gastos), inyecciones_capital, arqueos, ordenes_trabajo,
ot_repuestos, encargos, gastos_fijos, traspasos, config_finanzas, auditoria_dte.
El SQL vive en sql/, en migraciones numeradas que deben correrse en orden. La última es la 15.
Nunca recrees la base desde el "schema for context only" de Supabase: ese export lista tablas pero no incluye triggers, funciones ni secuencias. Recrear desde ahí rompe el número de OT (falta el trigger) y los costos FIFO (faltan las funciones). Corre siempre los archivos
sql/.
Guía completa en docs/README-DESPLIEGUE.md. Resumen del flujo:
sql/ que falten, en orden.
Todas son idempotentes.compras-documentos en privado (Storage → Buckets).SUPABASE_URL,
SUPABASE_SERVICE_ROLE_KEY, JWT_SECRET, ADMIN_PIN, WORKER_PIN, CORS_ORIGINS,
NEGOCIO_NOMBRE. Ver .env.example.curl .../api/health.css/tailwind.css ya viene compilado).Estado a v16. Lo que ya se completó (caja por turnos, despacho/envíos, escáner, ventas por pagar, gastos programados, buscador de ventas) está en los changelogs V12–V16 y en las secciones de este maestro.
Pendientes técnicos (no bloqueantes):
SELECT ... FOR UPDATE en Postgres.service_role
omite RLS). Es un refactor grande, opcional para un negocio de un solo local.escaparHTML, escaparTexto, etc.) en el único escHtml.id duplicado kpiUtilidadNeta: aparece en dos vistas de KPI (Balance e Historial). Preexistente,
no rompe nada visible, pero conviene renombrar uno para que ambos se actualicen siempre.api/index.js (~3800 líneas) e index.html siguen creciendo. En
algún punto conviene partir el backend en routers por dominio. Decisión tomada: no ahora, priorizar
features.Integración e-commerce (sevelin.cl), preparada pero no conectada:
tipo_entrega, estado_envio, numero_seguimiento…) y de comisión de
pasarela (origen_pago, comision_pasarela) ya existen y el POS las usa. Falta el sitio web que
cree ventas por la API con origen_pago = 'pago_web'.Corren en orden, todas idempotentes. Las más recientes:
16-ajustes-saldo-y-gasto-fijo.sql — ajustes manuales de saldo; vínculo gasto fijo ↔ compra.17-caja-diaria-y-despacho.sql — cajas_diarias, caja_movimientos; columnas de despacho y
comisión en ventas.18-gastos-programados.sql — gastos_programados (pendientes / cuotas de tarjeta de crédito).cerrarModal, descargarArchivo): dos archivos con el mismo
nombre de función, la segunda pisa a la primera en silencio. Dejó "Nuevo Producto" atrapado y bajó
respaldos con nombre y contenido invertidos. → Regla de oro (2.2).num() no definida en el frontend: el widget de saldos y los botones de Traspaso/Resguardo la
usaban, pero solo existía en el backend. ReferenceError abortaba la apertura del modal: los
botones "no hacían nada". → num ahora es helper global en config.js.numero_ot se perdió (base recreada desde el
diagrama). → migración 13, y la regla de no recrear desde el "schema for context" (sección 8).innerHTML sin escapar. →
escHtml en todos los módulos.-9000 cuadraba arqueos con un descuento falso.
→ rechazado en el servidor.disabled tras una venta. → bloque de controles elevado
con z-index y el checkbox re-habilita el campo siempre.confirmarEntrega (v14): una en ot.js (entrega de OT) pisaba a la de pago.js
(entrega de venta), y el cobro se colgaba tras el DTE. → las de venta se renombraron a
confirmarEntregaVenta / cancelarEntregaVenta. Reveló que el chequeo anti-colisión viejo no
capturaba funciones indentadas — ver el patrón corregido en 2.2.id de modal caja Finanzas vs POS (v13): modalAbrirCaja/modalCerrarCaja existían
en dos vistas; getElementById tomaba siempre el primero y los del POS quedaban muertos. → los del
POS se renombraron a modalAperturaPos/modalCierrePos.fechaHoyChile no existe en el frontend (v15): un módulo la referenció (solo está en el backend);
al no estar definida, lanzaba ReferenceError silencioso. → usar todayISO() (el helper real del
frontend, en config.js) y proteger con typeof.El sistema de documentación tiene tres piezas, cada una con un rol distinto:
docs/README.md (este archivo) — el maestro. Refleja siempre el estado actual completo:
arquitectura, reglas, módulos, modelo de datos, despliegue, backlog y bugs memorables. Es la fuente
de verdad. Se actualiza cuando un cambio altera algo estructural.
docs/CHANGELOG-Vx.md — el detalle por versión. Cada entrega añade el suyo. Describe solo lo
que cambió en esa versión y depende del maestro para el contexto (no repite la arquitectura).
docs/SNAPSHOT.md — el punto de retomada. Un resumen corto y accionable del estado ahora
mismo: en qué se está trabajando, qué se acaba de terminar, qué falta, y las trampas activas. Es lo
que se pega o se lee al abrir un chat nuevo o llevar el proyecto a otra IA. Se actualiza al
cerrar cada sesión de trabajo. A diferencia del maestro (exhaustivo), el snapshot es breve y va
directo a "por dónde sigo".
Regla de actualización al cerrar una sesión: actualiza el SNAPSHOT.md (siempre) y, si el cambio
fue estructural, la sección correspondiente del maestro. Crea el CHANGELOG-Vx.md de la versión.
Los README históricos (V5–V8) viven en docs/archivo/ como registro; este maestro los reemplaza
como fuente de verdad.