尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Web app Next.js 16 + Supabase para que operadores de sucursal carguen comprobantes (facturas de compra, recibos de cobranza) que luego se importan en ContaSystem desktop.
Diseño: "Modern Ledger" — ver
design/DESIGN_SYSTEM.mdpara tokens, tipografía y componentes.
@theme en app/globals.css@supabase/ssr (server) + @supabase/supabase-js (browser)next/fontProyecto Supabase con los schemas aplicados:
01_schema.sql (sync de socios — define user_grupos, user_empresas)05_comprobantes_online.sql (esta feature — tablas *_remoto + RPCs + RLS)../contasystem-desktop/docs/supabase/.Al menos una empresa con permite_comprobantes_online = 1 en SQLite local.
El usuario Supabase (operador o contador) tiene que estar asignado en public.user_empresas (o public.user_grupos) para las empresas que va a ver.
Catálogos subidos desde ContaSystem desktop:
cd contasystem-web-carga
npm install
# Copiá las credenciales del proyecto Supabase (Project Settings → API)
cp .env.local.example .env.local
# Editá .env.local con NEXT_PUBLIC_SUPABASE_URL y NEXT_PUBLIC_SUPABASE_ANON_KEY
npm run dev
Abrir http://localhost:3000 — redirige a /login.
/login — email/password (Supabase Auth)./empresa — selector de empresas habilitadas:
/carga.localStorage (cs-carga-empresa-id) y en sesiones siguientes salta directo./carga — pantalla principal:
pendiente / importado / rechazado.upsert_comprobante_web → fila nueva con numeración WEB-{empresa}-{YYYY}-{seq}.Reemplaza el pasaje de lista a mano en la puerta. El desktop emite un token opaco por inscripción, arma el QR y lo manda en el recibo de cobro o en la confirmación de inscripción; la web lo escanea.
Requisitos SQL (Supabase → SQL Editor), en este orden:
34_asistencia_qr.sql — vive en ../contasystem-desktop/docs/supabase/.
Crea entradas_remoto y los RPCs buscar_entrada /
marcar_asistencia_entrada.supabase/desmarcar_asistencia.sql —
agrega desmarcar_asistencia_entrada y la traza de la desmarca. Sin esto
todo funciona salvo el botón de desmarcar, que responde 501 diciendo qué
falta correr. En el desktop este archivo va como 35_desmarcar_asistencia.sql.También hace falta SUPABASE_SERVICE_ROLE_KEY: los RPCs son SECURITY DEFINER
y la tabla no está expuesta a anon.
| Ruta | Sesión | Qué hace |
|---|---|---|
/a/{token} | no | Muestra la entrada (evento, nombre, estado) y re-dibuja el QR. No marca nada. |
/checkin | sí | Escáner del staff: cámara → marca asistencia → resultado a pantalla completa. Pestaña Manual para pasar lista sin cámara. |
Abrir la URL del QR NO marca asistencia. La marca la hace personal del
evento desde /checkin, autenticado. Si bastara con abrir el link, cualquiera
se autoregistraría desde su casa — y un preview de link de WhatsApp o Gmail lo
dispararía solo. Por eso /a/{token} usa buscar_entrada, que es de sólo
lectura, y nunca marcar_asistencia_entrada.
| Resultado | Pantalla | Cuándo |
|---|---|---|
ok | Verde | Primer ingreso. |
ya_presente | Ámbar, "ya ingresó a las HH:MM" | Reescaneo, o check-in ya hecho a mano en el desktop. Suele ser alguien intentando entrar dos veces. |
anulada | Rojo | La inscripción se dio de baja. |
no_encontrada | Rojo | Token inexistente, de otra empresa, o QR ajeno. |
otro_evento | Rojo | Entrada válida pero de otro evento de la misma empresa (agregado de la web, no del SQL). |
El marcado es idempotente: reescanear no pisa la hora del primer ingreso.
asistio_por guarda el email de la sesión del operador, nunca un valor que
venga del navegador.
El respaldo sin cámara, y también la forma de pasar lista a mano. Marca por el
mismo endpoint que el escáner, así el resultado y la traza en asistio_por son
idénticos.
roles_evento:
arranca con Asistente / Expositor / Organización). "Todos" saca el filtro.
Una entrada sin rol asignado cuenta como asistente, igual que en el
desktop; si no, la mayoría no aparecería en la vista por defecto.Al desmarcar quedan desmarcada_at / desmarcada_por con la traza, y la
corrección es firme: upsert_entrada fue modificado para que un push posterior
del desktop no vuelva a marcar a la persona (antes, COALESCE(existente, entrante) la resucitaba en el próximo reenvío de recibo). Una marca
genuinamente nueva —un reescaneo después de la desmarca— sí entra.
Lado desktop: resuelto.
pullAsistenciasQRbaja también las desmarcas (segunda consulta sobredesmarcada_at) y resuelve por última escritura; y desde el 30/08/2026 una desmarca hecha EN el desktop viaja a la nube por la misma RPC (autoPushDesmarcaAsistencia). Verdocs/eventos-performance-propuesta.mden el repo del desktop.
entradas_remoto guarda un único nombre_completo armado como
"NOMBRE APELLIDO", así que para ordenar por apellido hay que deshacer esa unión.
No se parte con heurística: "MARÍA ELISA DE LEÓN" y "ANA GONZÁLEZ ROSSI"
tienen la misma forma y el corte va en lugares distintos. Se resuelve contra la
ficha del socio (socios_datos, que sí tiene los campos separados) usando el
documento de la entrada como clave. Quien no tenga ficha se muestra con el
nombre completo tal cual y se ordena por él.
localhost). En http:// no hay visor —
el control manual sigue funcionando.No crea, no borra ni reconcilia entradas: el desktop es dueño del alta y del
estado. Lo único que la web escribe es la asistencia — asistio_at /
asistio_por / asistio_origen y la traza desmarcada_* — y siempre vía
marcar_asistencia_entrada o desmarcar_asistencia_entrada.
contasystem-web-carga/
├── app/
│ ├── layout.tsx # fuentes + Toaster
│ ├── globals.css # tokens Tailwind v4 + utilidades (.field, .btn-primary, .badge…)
│ ├── page.tsx # redirect → /login o /empresa según sesión
│ ├── login/page.tsx # signIn Supabase
│ ├── empresa/page.tsx # selector + localStorage
│ ├── (app)/carga/page.tsx # form + lista últimos 20
│ ├── a/[token]/page.tsx # vista PÚBLICA de una entrada (destino del QR)
│ ├── (app)/checkin/ # escáner del staff (cámara + control manual)
│ └── api/checkin/ # eventos · marcar · desmarcar · entradas
├── components/
│ ├── Header.tsx # header persistente con dropdown user
│ ├── Highlight.tsx # subrayado ámbar (una palabra por pantalla)
│ └── Stamp.tsx # sello decorativo (solo login)
├── lib/
│ ├── format.ts # formato uruguayo de números/fechas + hora Montevideo
│ ├── types.ts # tipos TS de tablas Supabase
│ ├── entradas.ts # RPCs de asistencia (server-only)
│ ├── entradas-types.ts # tipos de entradas/check-in (client-safe)
│ ├── checkin-token.ts # URL del QR o token pelado → token
│ ├── checkin-auth.ts # scope por empresa de /api/checkin/*
│ ├── qr.ts # QR en SVG (server-only)
│ └── supabase/
│ ├── client.ts # createBrowserClient
│ ├── server.ts # createServerClient (Server Components)
│ └── middleware.ts # updateSession para middleware.ts
├── proxy.ts # gate de auth (middleware de Next 16; excluye las rutas públicas)
├── design/ # ← mockup HTML, design system doc, primitivos JSX
└── .env.local.example
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY (formulario público de eventos, tracking, elecciones) y CRON_SECRET (cron de cumpleaños). Ver .env.local.example.| Problema | Causa probable |
|---|---|
"No tenés empresas asignadas" en /empresa | Falta entrada en public.user_empresas para tu UUID + empresa_id |
Plantillas vacías en /carga | El contador no subió catálogos todavía (ContaSystem → Sync → "Subir catálogos a la web") |
new row violates row-level security policy al guardar | El user no tiene acceso a la empresa que viene en el payload (RLS) |
| Error 500 en RPC | La empresa o la plantilla referenciada no existe en remoto |
/checkin no abre la cámara y avisa de HTTPS | La página está servida por http://. getUserMedia sólo corre en contexto seguro: usar la URL de Vercel o localhost |
/checkin muestra "todavía no hay entradas con QR emitidas" | El desktop no subió ninguna entrada para esa empresa, o el evento es más viejo que la ventana del selector (?dias=, default 60) |
Could not find the function public.buscar_entrada | Falta correr 34_asistencia_qr.sql |
| "Falta correr supabase/desmarcar_asistencia.sql" al desmarcar | Justamente eso: correr ese archivo en el SQL Editor |
| Se desmarcó en la web pero el desktop la sigue viendo presente | Esperado por ahora: el pull del desktop todavía no baja las desmarcas (ver el pie de supabase/desmarcar_asistencia.sql) |
Todo escaneo da no_encontrada | La empresa activa de la web no es la dueña de esas entradas (el marcado está scopeado por empresa) |
| Falta gente en la lista Manual | El filtro está en un rol. Por defecto muestra sólo Asistente: tocar Todos |
| Alguien aparece sin "APELLIDO, Nombre" al ordenar por apellido | Su documento no resuelve contra socios_datos (sin ficha, o documento distinto). Se muestra el nombre completo tal cual |
Si los problemas persisten, revisar el SQL Editor de Supabase y verificar:
-- Tenants asignados al user logueado
SELECT 'empresa' AS tipo, empresa_id AS id FROM public.user_empresas WHERE user_id = (SELECT id FROM auth.users WHERE email = '[email protected]')
UNION ALL
SELECT 'grupo' AS tipo, grupo_id AS id FROM public.user_grupos WHERE user_id = (SELECT id FROM auth.users WHERE email = '[email protected]');
-- Catálogos visibles
SELECT * FROM public.empresas_online_remoto;
SELECT id, nombre_razon_social, tipo FROM public.contactos_remoto;
SELECT id, nombre, iva_porcentaje, activo FROM public.plantillas_remoto;