尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
PWA offline-first para comerciantes de mercados de abastos. Inventario inteligente, ventas con ticket (imprimir / WhatsApp), semáforo de frescura y registro de mermas a un tap — pensada para manos ocupadas, luz solar directa y conexión inestable. Stack: Next.js 16 (App Router, TypeScript) · Tailwind CSS · shadcn/ui · Lucide · Supabase (Postgres + Auth + RLS) · Dexie/IndexedDB · React Query · Zustand · Web Speech API.檢視完整 README ↗
PWA offline-first para comerciantes de mercados de abastos. Inventario inteligente, ventas con ticket (imprimir / WhatsApp), semáforo de frescura y registro de mermas a un tap — pensada para manos ocupadas, luz solar directa y conexión inestable.
Stack: Next.js 16 (App Router, TypeScript) · Tailwind CSS · shadcn/ui · Lucide · Supabase (Postgres + Auth + RLS) · Dexie/IndexedDB · React Query · Zustand · Web Speech API.
npm install
cp .env.example .env.local # opcional: rellena tus claves de Supabase
npm run dev # http://localhost:3000
Sin backend también funciona. Si no configuras Supabase, la app corre en modo local: la primera vez crea un acceso protegido con clave en el dispositivo y guarda todo en IndexedDB (puedes cargar productos de ejemplo desde el inventario vacío). Con Supabase configurado, el login es con correo y contraseña y los datos se respaldan en la nube.
| Modo | Cuándo | Cómo ingresa |
|---|---|---|
| Supabase | .env.local con URL y anon key | Correo + contraseña (registro incluido). La sesión persiste y funciona sin internet. |
| Local | Sin .env.local | Una cuenta por dispositivo con clave (PBKDF2, con bloqueo tras 5 intentos). Protege el acceso a la app; los datos de IndexedDB no están cifrados. |
En Supabase, si activas Confirm email, el usuario debe confirmar su correo antes de ingresar.
Cada venta (rápida o desde Ventas → Nueva venta) genera un ticket numerado con detalle, descuento, método de pago (efectivo/Yape/Plin/tarjeta) y vuelto. Desde el ticket puedes imprimir (58 mm u 80 mm, o "Guardar como PDF"), enviar por WhatsApp (al celular del cliente si lo registraste), copiar o compartir. Ajusta papel, dirección, RUC/DNI y mensaje en Ajustes; ahí mismo hay un ticket de prueba. El ticket es de control interno, no reemplaza una boleta o factura electrónica SUNAT.
Formas de imprimir
| Opción | Cuándo usarla |
|---|---|
| Bluetooth directo (ESC/POS) | Impresora térmica BLE con Chrome/Edge (Android o PC, HTTPS o localhost). Empareja una vez en Ajustes → Impresora; después imprime con un toque. No funciona en iPhone/iPad. |
| RawBT (solo Android) | Impresoras Bluetooth clásicas (SPP), que Web Bluetooth no ve. Instala la app RawBT y usa el botón del ticket. |
| Diálogo del sistema / PDF | Cualquier impresora instalada en el dispositivo (USB, Wi‑Fi, servicio de impresión de Android) o «Guardar como PDF». |
El ticket ESC/POS usa la página de códigos PC858 (acentos, ñ, ¿, ¡). Activa Cortar el papel solo si tu impresora tiene cortador.
Con Supabase, el dueño agrega cajeros por correo en Ajustes → Equipo (la persona debe haberse registrado antes). El cajero puede vender, ver el inventario y cobrar fiados; no puede anular ventas, ver reportes, editar/reabastecer productos, registrar mermas, cerrar caja ni cambiar el negocio. Se aplica en la interfaz, en los repositorios y en la base de datos (RLS).
/recuperar) y cambio desde Ajustes → Seguridad.npm run build
npm run start
supabase/migrations/0001_init.sql — 6 tablas, enums, índices, RLS
(aislamiento total por comerciante) y triggers de stock, PPC, merma y timestamps.supabase/migrations/0002_sales.sql — tablas sales y sale_items (tickets).
Sin este script las ventas no se sincronizan.supabase/migrations/0003_team_credit_reports.sql — stock mínimo, costo por
línea de venta, anulaciones, fiado (clientes y abonos), equipo con roles y las
políticas RLS actualizadas. Ejecútalo antes de usar la app con Supabase: el
cliente ya envía estos campos y, sin el script, la sincronización se detiene.http://localhost:3000) a Redirect URLs.anon key (Project Settings → API) a .env.local.El semáforo de frescura se recalcula tanto en el cliente (offline) como en el
servidor vía la función fn_recalc_freshness(owner).
mercadocontrol/
├─ supabase/migrations/0001_init.sql # Esquema + RLS + triggers
├─ public/
│ ├─ manifest.json # PWA
│ └─ sw.js # Service Worker (offline + background sync)
├─ src/
│ ├─ app/ # Rutas (App Router)
│ │ ├─ layout.tsx providers.tsx # Raíz: fuente, React Query, AuthProvider
│ │ ├─ (auth)/login/ # Pantalla pública de acceso
│ │ └─ (app)/ # Pantallas privadas (protegidas por SessionGate)
│ │ ├─ page.tsx # Dashboard
│ │ └─ inventario/ ventas/ mermas/ cierre/ remates/ ajustes/
│ ├─ components/
│ │ ├─ ui/ # Primitivas (button, card, sheet, stepper, feedback…)
│ │ ├─ layout/ # AppShell, Sidebar, TopBar, BottomNav, FAB
│ │ ├─ auth/ ticket/ sales/ settings/
│ │ ├─ dashboard/ inventory/ mermas/ cierre/
│ ├─ lib/
│ │ ├─ auth/ # Contrato AuthService + Supabase / Local
│ │ ├─ supabase/client.ts # Cliente Supabase
│ │ ├─ db/ # Dexie, repositorios (productos, ventas, ajustes), sync, sesión
│ │ ├─ print/ # Impresión por iframe aislado
│ │ └─ logic/ # Lógica PURA y testeable: frescura, PPC, ticket, carrito,
│ │ # reportes, WhatsApp, parser de voz
│ ├─ hooks/ # React Query + Web Speech API
│ ├─ stores/ # Zustand (estado de UI)
│ └─ types/ # Tipos de dominio
syncQueue como mutación pendiente.lib/db/sync.ts) vacía la cola hacia Supabase con
upsert idempotente cuando hay conexión, disparado por el evento online o
por Background Sync del Service Worker.Hecho para el mercado. 🇵🇪