Aseryx
데이터를 AI 기업에 라이선스하고, 모든 데이터셋은 공유 전 팀 검토를 거쳐 품질 인증을 받습니다
과테말라 레스토랑용 디지털 메뉴를 만들고 QR, 일일 메뉴 예약, WhatsApp 주문을 지원합니다 14일 무료 체험
Crea el menú digital de tu restaurante en Guatemala: QR, menú del día programado y pedidos a tu WhatsApp. Prueba gratis 14 días.
커머스TypeScript · CSSGitHub @Alex-Gallardo온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Menú digital QR multi-negocio y multi-sucursal con pedidos por WhatsApp
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Menú digital QR multi-negocio y multi-sucursal: cada restaurante obtiene un menú (link + QR por sucursal) instalable como app, donde el comensal arma su pedido en 3 toques y lo envía por WhatsApp a la sucursal. Mercado inicial: Guatemala (GTQ, español, America/Guatemala).
El nombre comercial visible se configura con
NEXT_PUBLIC_BRAND_NAME(verlib/brand.ts).
Next.js 16 (App Router) · TypeScript strict · Tailwind CSS v4 + shadcn/ui · Supabase (Postgres, Auth, Storage, Realtime) · Prisma · Zustand · TanStack Query · React Hook Form + Zod · Vitest · Playwright.
.nvmrc)npm i -g pnpm o corepack enable pnpm)pnpm install
cp .env.example .env.local
pnpm dev
Abre http://localhost:3000. Para probar todo lo construido (usuarios de prueba, rutas y lista de verificación) sigue docs/verificacion.md.
Hay dos proyectos de Supabase: dev (desarrollo y previews) y prod (producción). Cada uno tiene sus propias llaves; nunca se mezclan.
us-east-1)..env.local.DATABASE_URL: Transaction pooler (puerto 6543, con ?pgbouncer=true). La usa la app en runtime.DIRECT_URL: Session pooler o conexión directa (puerto 5432). La usan las migraciones.pnpm db:migrate # aplica las migraciones de Prisma (37 tablas)
pnpm db:sql # aplica el SQL de Supabase (funciones, triggers, RLS, Storage, Realtime)
pnpm db:verify # comprueba tablas, RLS, políticas, triggers, bucket y Realtime
pnpm db:seed # planes, ajustes, negocio demo y usuarios de prueba
En producción: pnpm db:deploy (= prisma migrate deploy + db:sql) y SEED_SCOPE=demo pnpm db:seed.
pnpm db:seed es idempotente: actualiza planes y ajustes (sin pisar los que ya editó el super admin) y vuelve a crear el negocio demo.
Alcance (SEED_SCOPE) | Crea | ¿Producción? |
|---|---|---|
base | 3 planes (Gratis Q0, Pro Q149, Negocio Q299) y platform_settings (slugs reservados, días de prueba y gracia, fuentes, paletas, bienvenida) | Sí |
demo | base + Café El Portal (/cafe-el-portal): 2 sucursales (Zona 1 y Antigua), licencia de prueba Negocio, 3 franjas, 3 menús con programación, 25 productos con foto, "Pasta con mariscos" agotada solo en Antigua, 2 cuentas bancarias, 8 QR y 30 pedidos (5 a domicilio con coordenadas) | Sí (es la demo de ventas) |
dev (por defecto) | demo + usuarios de prueba (admin de plataforma, dueño, encargado solo de Antigua) y 1 staff con PIN por sucursal | No (el seed se niega) |
Los usuarios de prueba usan SEED_USERS_PASSWORD de .env.local; si no existe, el seed la genera y la guarda ahí. Pon tu número en SEED_DEMO_WHATSAPP para recibir los pedidos del demo; por defecto es un número inválido para no escribirle a un desconocido.
lib/env.ts valida las variables con Zod en el primer uso y muestra cuáles faltan, sin imprimir sus valores. El cliente Prisma se genera en generated/ (ignorado por git) al instalar dependencias.
| Qué | Dónde | Por qué |
|---|---|---|
| Tablas, enums, relaciones e índices | prisma/schema.prisma → prisma/migrations/ | Prisma los genera y versiona |
Índices parciales y CHECK (p. ej. one_default_menu, rating_range) | Migración SQL manual en prisma/migrations/ | Postgres estándar que Prisma no modela; se replica en la base shadow |
Funciones con auth.uid(), triggers sobre auth.users, RLS, Storage y Realtime | prisma/sql/*.sql (idempotentes, en orden) | Dependen de Supabase; la base shadow de Prisma no tiene el esquema auth |
Regla de RLS: RLS está activo en todas las tablas. Prisma se conecta como dueño y no pasa por RLS (la protección en servidor es requireAccess). Solo se abren políticas para lo que el navegador toca directo con la llave pública: pedidos (Realtime) y archivos (Storage). Una tabla nueva sin política queda cerrada por defecto: al agregar tablas, corre pnpm db:sql para activar su RLS.
| Ruta | Quién entra | Cómo se protege |
|---|---|---|
/entrar, /registro, /recuperar | Cualquiera (con sesión, /entrar y /registro llevan al panel) | — |
/panel/**, /onboarding | Usuario con email | proxy.ts (sin sesión → /entrar?next=…) + verificación de membresía en el servidor |
/admin/** | Equipo de la plataforma con 2FA (TOTP) | proxy.ts + requirePlatformUser() (sin rol → 404, sin 2FA → /entrar/2fa) |
/staff/** | Dispositivo autorizado, turno con PIN o dueño/encargado | proxy.ts (cookie del dispositivo o sesión) |
| Menú público | Cualquiera | — |
proxy.ts, el antiguo middleware): refresca la sesión de Supabase y decide con lib/auth/route-rules.ts (función pura, con pruebas). Los permisos finos se validan siempre en el servidor.platform_settings.maintenance_mode = {"enabled": true, "message": "…"}. Todo responde 503 con el mensaje, salvo /admin, /entrar, /auth y /api. Se lee con caché de 30 s.Configuración en Supabase (Authentication → URL Configuration):
http://localhost:3000/auth/callback y https://*-alexgallardos-projects.vercel.app/auth/callback (previews).Nada se habilita solo en el frontend. Toda Server Action sigue este orden:
"use server";
export async function createBranch(input: unknown): Promise<ActionResult> {
const parsed = branchSchema.safeParse(input); // 1. Zod
if (!parsed.success) return { ok: false, message: "Revisa los datos." };
try {
await requireAccess(businessId, { roles: ["owner"], write: true }); // 2. sesión, rol, sucursal, licencia
await assertWithinLimit(businessId, "branches"); // 3. límite del plan
// 4. escribir con Prisma → 5. audit_logs → 6. revalidateTag
return { ok: true };
} catch (error) {
return actionError(error); // AccessError / LimitExceededError → mensaje amable
}
}
| Pieza | Archivo | Qué hace |
|---|---|---|
getEntitlements(businessId) | features/licensing/server.ts | Plan + ajustes del super admin + estado efectivo (prueba, activa, gracia, gratis, suspendida, cancelada). Aplica vencimientos aunque el cron no haya corrido |
assertWithinLimit(businessId, recurso) | features/licensing/server.ts | Sucursales, productos, menús, franjas, QR, encargados, staff y MB. Mensaje: "Tu plan incluye 1 sucursal. Pasa a Negocio para agregar más." |
requireAccess(businessId, { roles, branchId, feature, write }) | lib/auth/require-access.ts | Sesión → membresía → sucursal del negocio → rol → licencia → función |
| Lógica pura | features/licensing/entitlements.ts, limits.ts, lib/auth/access.ts | Sin base de datos: con pruebas unitarias |
Vercel (plan Pro: el gratuito no permite uso comercial):
Alex-Gallardo/qr-menu. Framework: Next.js (detectado); comando de instalación pnpm install.master. Cada PR y la rama develop generan una URL de preview.iad1 (vercel.json), la misma región que Supabase us-east-1.https://<dominio>/api/health: dice qué variable o paso falta (solo sí/no, nunca valores). Al cambiar una NEXT_PUBLIC_* hay que volver a desplegar, porque se incrusta al compilar.Sentry (plan gratuito):
NEXT_PUBLIC_SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT y SENTRY_AUTH_TOKEN (Settings → Auth Tokens en Sentry, con permiso de releases y source maps). El token va solo en Vercel./api/monitoring para que los bloqueadores no los descarten.sendDefaultPii: false (sin IPs, cookies ni datos del comensal).| Script | Qué hace |
|---|---|
pnpm dev | Servidor de desarrollo |
pnpm build | Build de producción |
pnpm lint | ESLint (sin warnings permitidos) |
pnpm typecheck | Genera tipos de rutas y corre tsc |
pnpm format | Formatea con Prettier |
pnpm test | Pruebas unitarias con Vitest |
pnpm test:integration | Pruebas contra Supabase dev (requiere pnpm db:seed) |
pnpm test:e2e | E2E con Playwright (C1–C3, D1, S1, S2, A2) contra el build (pnpm build antes) |
pnpm test:lighthouse | Lighthouse CI móvil (landing, precios, legal; LHCI_MENU=1 suma el menú) |
pnpm check | Lint + typecheck + formato + tests |
pnpm db:migrate | Crea y aplica migraciones (dev) |
pnpm db:sql | Aplica el SQL de Supabase (RLS, etc.) |
pnpm db:verify | Verifica tablas, RLS y políticas |
pnpm db:deploy | Migraciones + SQL en producción |
pnpm db:seed | Datos semilla (planes, demo, usuarios) |
pnpm db:status | Estado de las migraciones |
pnpm db:studio | Explorador visual de la base de datos |
| Rama | Uso |
|---|---|
master | Producción. Solo recibe merges desde develop por release |
develop | Integración y pruebas |
feat/QRM-0XX-nombre | Una rama por ticket, sale de develop y vuelve por PR |
feat(branches): crear sucursal con horarios. Lo valida commitlint en el hook commit-msg.pre-commit corre ESLint + Prettier sobre los archivos en stage; pre-push corre el typecheck.develop y debe pasar el CI (lint, typecheck, formato, tests, build y detección de secretos).develop → master y se etiqueta la versión.kebab-case, componentes en PascalCase, rutas visibles en español.features/<dominio>/ (componentes, actions.ts, queries.ts, schemas.ts).*_cents)..env.example documenta cada una).