Synaplan
开源 AI 软件平台,面向企业:自托管 AI 代理,符合 GDPR 的聊天机器人小部件,RAG 知识库,WhatsApp 与 Outlook AI,MCP,多模型路由。支持 Docker、Kubernetes 或在 Germ 托管。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Al guardar un dato, el formulario se limpiaba y la fecha volvía a hoy. Quien estaba cargando varios registros de otro día tenía que ir al calendario en cada uno.
Ahora el campo recuerda la última fecha elegida y vuelve a ella después de guardar. Dura lo que dura la app abierta (sessionStorage): al volver a entrar mañana el campo dice hoy otra vez, no el día que se estuvo corrigiendo.
Y de paso, un error que empeoraba justo lo mismo. El "hoy" se calculaba con toISOString(), que da la fecha en UTC — tres o cuatro horas adelante de Chile. A partir de las 20:00 o 21:00 el campo aparecía con el día siguiente, y en un casino que trabaja de noche eso es casi todos los turnos: había que corregir la fecha incluso para registrar el día en curso.
Medido con el reloj puesto a las 22:30 del 3 de septiembre en horario de Chile: la forma vieja decía 2026-09-04; la nueva dice 2026-09-03.
Nueve comprobaciones automáticas, incluidas la de la zona horaria y la de que no se caiga si el navegador bloquea el almacenamiento (modo privado).
Los botones del Historial salían en blanco, sin color ni icono, al lado de otros que sí los tenían. Ahora el color dice de qué tipo es cada acción, en vez de ser decorativo:
| Índigo — controles de vista | Expandir/Min · Reciente · Antiguo · Dos columnas |
| Verde — acción segura | Exportar Backup |
| Ámbar — hay que fijarse | Restaurar JSON · Guardar Período |
| Rojo — destructiva | Vaciar Todo |
Los cuatro de vista van del mismo color a propósito: son la misma clase de acción, y darle un color distinto a cada uno sería ruido.
Iconos de Font Awesome 6 acordes al tamaño que ya tenían (btn-sm): fa-maximize, fa-arrow-down-wide-short, fa-arrow-up-short-wide, fa-table-columns, fa-file-export, fa-file-import, fa-trash-can.
De paso: «Dos columnas» usaba el carácter ▦ en vez de un icono, y «Restaurar JSON» y «Vaciar Todo» no tenían ninguno, quedando dispares junto a sus vecinos.
Un detalle que habría roto el cambio al primer clic: hist_aplicarVista() reescribía ese botón con textContent, lo que borra el HTML — el icono habría desaparecido apenas se alternara la vista. Pasó a innerHTML.
Verificado que en pantalla de 390px los botones siguen sin desbordar.
Al llegar una nota nueva, su tarjeta quedaba blanca mientras el texto seguía claro: el mensaje recién llegado —justo el que uno quiere leer— era el único ilegible de la lista. Lo mismo pasaba con las notas fijadas.
La causa. El fondo de esas dos lo escribía a mano app.js al armar el HTML: background:#eff6ff para la nueva y #fffde7 para la fijada. Colores claros fijos, sin variante de tema. Las notas normales no lo tenían (background:''), así que esas sí tomaban el color del tema y se veían bien — por eso el problema era solo del mensaje nuevo.
Ahora el fondo sale de una clase (es-nueva / es-fijada) y cada tema pone el suyo. Los tonos oscuros son el mismo azul y el mismo ámbar, rebajados hasta que el texto pase holgado: 13:1 el mensaje y 6,9:1 la línea del autor.
Y un segundo caso que estaba escondido. Los botones de reacción (👍 ❤️ 😂) tenían fondo #f8fafc fijo y el número de la cuenta no traía color propio, así que heredaba el claro del tema oscuro. Con cero reacciones no se notaba —solo se ve el emoji—, pero apenas alguien reaccionaba el número quedaba blanco sobre blanco. También pasó a clases con variables.
De paso, la línea «Por X — fecha» usaba --text-muted, que sobre el azul de la nota nueva daba 4,37:1, bajo el mínimo de 4,5. Tiene su propio gris, un punto más oscuro: ahora da entre 5,5 y 5,9:1 en los tres fondos, y mejora también en las notas normales.
Medido en los tres temas, con una nota nueva, una fijada y una normal: 36 comprobaciones, todas por encima del mínimo.
Archivos: app.js, styles.css, index.html, sw.js, version.js.
Los dos números que esta app muestra junto al divisor no eran los mismos que los del panel de Gestión de Socios. Esta app se había quedado con la regla vieja.
| Cuándo empiezan a contar los puntos | |
|---|---|
| socios-comicion (correcto) | El primer día 15 que cae en o después de cumplir el primer mes de contrato |
| diario.propi (lo que hacía) | El día 15 del mes de ingreso |
Con el Ejemplo A de la política —ingresa el 4 de septiembre— la comisión le da los puntos el 15 de octubre y acá se los contaba desde el 15 de septiembre: un mes antes. Esos puntos de más inflaban el Total y, si era de Planta, también el Pts Planta.
No era un caso aislado. Probado sobre las 365 fechas de ingreso de un año contra dos años de días de observación: las 365 se separaban en algún momento, el 6,3 % de las combinaciones. La regla vieja también corría el aniversario anual, porque lo anclaba al mes de inicio de puntos en vez de al mes de ingreso.
El arreglo: se copiaron las reglas reales de socios-comicion/js/constants.js (reglaPuntosArea, reglaPuntosFechas, aniosPuntosA) y de js/api.js (procesarSocioDesdeGoogle), incluido el detalle de que fecha_inicio_puntos solo manda cuando difiere de la fecha de ingreso — si viene igual no es una excepción manual, es un eco del ingreso.
Después del cambio: 293.460 combinaciones de fecha, área y puntaje, ninguna distinta.
Por qué está copiado y no compartido. Son dos despliegues distintos y no comparten archivos. Copiar ya costó este error una vez, así que si mañana cambia la política hay que cambiar los dos lados. La prueba
test_puntos_dos_apps.jscorre las dos implementaciones sobre los mismos socios y falla si vuelven a separarse.
Se revisó también propi.solicitada: ya estaba alineada (7 áreas y 76.285 fechas comparadas, todas iguales).
Archivos: supabase-api.js, index.html, sw.js, version.js.
Se cambió el ícono y en los teléfonos no pasó nada. No es una falla: cambiar los íconos del manifiesto no cambia el que está pegado en la pantalla de inicio. El teléfono copió el ícono el día que se instaló la app y se queda con esa copia.
| Dónde | Qué pasa con el ícono viejo |
|---|---|
| Pestaña del navegador | Se actualiza solo, apenas se recarga |
| Android, app instalada | Chrome revisa el manifiesto como mucho una vez al día y programa la actualización por su cuenta: puede demorar días, y a veces no ocurre |
| iPhone, app instalada | No cambia nunca. Safari copia el ícono al agregar a inicio y ahí queda |
| Computador, app instalada | Tampoco cambia hasta reinstalar |
La única forma segura es sacar la app de la pantalla de inicio y volver a agregarla — no se pierde nada: ni los datos, ni la sesión, ni lo que está en la nube. Y eso no hay código que lo haga solo: tiene que hacerlo la persona.
Por eso ahora la app avisa. Sale una ventana una sola vez, con los pasos para Android y para iPhone, y solo a quien cumpla las tres condiciones:
La decisión de si corresponde o no se toma una sola vez, la primera vez que corre este código en el teléfono, y queda guardada. Si se preguntara en cada arranque, alguien que instale la app de aquí en adelante —que ya recibe el ícono nuevo— terminaría viendo el aviso igual en cuanto juntara datos de uso.
Cuidado al elegir las claves que delatan el uso previo.
diario_temaydiario_login_help_v2las escribe la app sola al abrirse por primera vez, así que un teléfono recién estrenado también las tiene. Solo sirven claves que aparecen cuando alguien de verdad entró y trabajó:_rec_last_seen,_rec_my_reactions,diario_last_backup_day,lastNote.
Es código con fecha de vencimiento: pasado el 31-03-2027 no se muestra más y el bloque se puede borrar entero.
Archivos: index.html, sw.js, version.js.
Hasta ahora esta app y la de la comisión usaban el mismo ícono —la marca del desarrollador—, así que instaladas una al lado de la otra en el teléfono no había cómo distinguirlas.
Ahora Recaudaciones tiene el suyo: la ficha circular roja con el signo peso, el corazón y la palabra TIPS, recortada justo en el borde del aro y con todo lo de afuera transparente. Se reemplazaron los cinco tamaños del manifiesto (192, 256, 384, 512 y el maskable de 512).
El maskable va aparte: la ficha al 80 % del lienzo sobre el rojo del aro (#933131), que es la zona segura de Android. Así, recorte el sistema un círculo, un cuadrado redondeado o una gota, la ficha nunca queda cortada.
Los nombres de archivo (icono-cpn-*.png) se dejaron tal cual: cambiarlos obligaba a tocar el manifiesto, el index.html y la lista de caché del Service Worker sin ganar nada.
Archivos: icons/icono-cpn-192x192.png, -256x256, -384x384, -512x512, -maskable-512, sw.js, version.js, index.html.
Optativo, y el PIN nunca se va. En la pantalla de ingreso aparece 👆 Entrar como Nombre solo si ese dispositivo tiene la huella registrada; el formulario con área, nombre y PIN sigue completo arriba.
Se activa desde adentro, después de entrar con el PIN. El interruptor está en la barra lateral, sobre «Cerrar Sesión». Se apaga cuando se quiera, y es por dispositivo.
Qué se guarda: el identificador de la credencial que crea el dispositivo, más a quién corresponde (id, nombre, área y foto) para poder abrir la sesión igual que el ingreso con PIN. La huella nunca sale del sensor. El PIN no se guarda en ninguna parte.
Qué NO es, y acá importa más que en Horarios: no hay servidor que verifique la firma — es un candado local. Y el PIN de diario.propi sí se verifica en el servidor en cada ingreso; entrando con huella ese paso no ocurre. Lo que vale entonces es que el dispositivo reconoció a su dueño y que antes, en ese mismo dispositivo, alguien entró con el PIN correcto — sin eso no se puede activar. El ingreso queda en la auditoría como huella_diario, distinto de pin_diario, para que se note cuál fue.
Verificación: 18 comprobaciones con un sensor biométrico virtual del navegador — que sin activarla la pantalla de ingreso no cambie; que sin haber entrado con PIN no deje activarla; que al registrarla guarde el identificador y nunca el PIN (revisando todo el almacenamiento del dispositivo); que al entrar deje la sesión idéntica a la del PIN, foto incluida; que si la huella no coincide no abra sesión; apagarla; y que sin sensor ni se ofrezca.
Archivos: biometria.js (nuevo), app.js, index.html, sw.js, version.js.
Síntoma: con el tema oscuro, el recuadro de la casilla aparecía blanco y su texto casi invisible encima.
Causa, y es mía: escribí var(--card-bg, #fff). En diario.propi la variable se llama --bg-card, no --card-bg. Al no existir, el navegador usaba el respaldo #fff y pintaba el recuadro blanco — mientras el texto, que sí usaba variables correctas, se quedaba claro.
Vino de copiar el patrón desde socios-comicion, donde la variable sí se llama --card-bg. Las dos apps usan nombres distintos para lo mismo: --bg-card/--text-main acá, --card-bg/--text-color allá.
Arreglado, y de paso todas las de su clase:
--card-bg → --bg-card, con el color de texto puesto explícito (--text-main) para no depender de lo que herede.#desgPanel toma el fondo de la tarjeta en vez de quedar transparente.--bg-card y opacidad, no con un gris fijo.#fff, #e2e8f0, #cbd5e1) que tapaban el problema: ahora si una variable falta, se nota.var(--text-color) en .vp-puntos b, el mismo error en la línea de Pts Planta.Verificación: 16 mediciones de contraste reales, tomadas con los colores que calcula el navegador en los tres temas. Todas por encima del mínimo WCAG — lo más justo es 4,47:1 donde se pide 3. El recuadro mide 255,255,255 en claro, 30,41,59 en oscuro y 20,20,20 en negro: ya no hay blanco fijo.
Se revisaron las otras dos apps por el mismo error: socios-comicion usa los nombres correctos y propi.solicitada no usa variables en ese código. El fallo era solo acá.
Archivos: styles.css, desglose.js, index.html, sw.js, version.js.
Síntoma: en el Bloc de Notas aparecían los códigos de formato como texto, en vez del texto con formato:
<div style="text-align:center"><b>Aviso importante</b></div><div>Turno <u>cambiado</u></div>
Causa: las notas se escriben en socios-comicion con un editor que permite negrita, cursiva, subrayado, alineación y listas, y se guardan como HTML. Acá se pintaban con escHtml(), que es justamente lo contrario: convierte las etiquetas en texto visible.
Arreglo: se portó el mismo criterio que ya usaba propi.solicitada con estas notas — las que traen formato se pintan saneadas y las de texto plano siguen igual que antes.
b, strong, i, em, u, br, p, div, span, ul, ol, li, a y los estilos text-align, font-weight, font-style, text-decoration. Todo lo demás se convierte en texto.http/https — javascript: y data: se descartan, dejando el texto.white-space: pre-wrap queda solo en las notas de texto plano, que lo necesitan para sus saltos de línea. En las que traen formato agregaría líneas en blanco de más, porque el HTML ya trae sus propios párrafos.Verificación: 28 comprobaciones, con las notas entrando por el camino real de la app (window._diarioReload), no inyectadas a mano. Que no quede ninguna etiqueta visible; que la negrita pese 700, el subrayado tenga underline, la cursiva sea italic, el centrado y la alineación a la derecha se apliquen y la lista tenga sus viñetas; que las notas viejas conserven su pre-wrap; y que una nota con <script>, una imagen con onerror, un enlace javascript: y un position:fixed no ejecuten nada ni dejen rastro, pero sí conserven su texto legítimo.
Archivos: app.js, index.html, sw.js, version.js.
El Manual de Uso tenía 4 secciones y se había quedado atrás: nada del conteo por denominación, del ingreso por QR, de la franja de sesión, de los temas ni de las notificaciones. Ahora son 9.
Lo que se agregó:
En los «?»: se agregó el del conteo por denominación, junto a la casilla, y el de Categorías ahora avisa que el tipo decide qué se cuenta — fichas en Sala de Juegos, billetes en los otros tres. Van 6 tips.
Verificación: 21 comprobaciones — que el manual tenga las 9 secciones y explique las 11 cosas que faltaban, que los tips abran con contenido, y que el de categorías nombre las fichas. (El de divisor vive en una tarjeta del historial y solo existe con datos cargados; eso es así por diseño y la prueba lo distingue.)
Archivos: index.html, app.js, sw.js, version.js.
Además de anotar el monto a mano, ahora se puede cargar el conteo por denominación al registrar. Es opcional: el formulario de siempre no cambió.
Con el conteo cargado pasan dos cosas: el monto deja de tipearse —sale de la suma, así que no puede quedar un número que no cuadre con lo que hay en el sobre— y lo contado viaja a socios-comicion, donde la verificación llega con las cantidades ya puestas y el encargado solo revisa y confirma.
Son dos juegos de denominaciones, no uno:
| Tipo | Qué se cuenta |
|---|---|
| Sala de Juegos | Fichas: 1.000.000 · 500.000 · 200.000 · 100.000 · 50.000 · 20.000 · 10.000 · 5.000 · 1.000 · 500 |
| TarjetaMDA, EfectivoMDA, Bóveda | Billetes y monedas: 20.000 · 10.000 · 5.000 · 2.000 · 1.000 · 500 · 100 · 50 · 10 |
La grilla se vuelve a pintar al cambiar el tipo: dejar la anterior haría contar sobre casillas que ya no corresponden.
Las fichas van en su propio campo (fichas_declaradas), aparte de los billetes (billetes_declarados). En Sala de Juegos la noche cuenta fichas pero a la bóveda llega efectivo, así que las fichas no deben entrar nunca al arqueo de caja. Teniéndolas separadas en la base, el código del arqueo —que solo lee el conteo verificado— no puede tomarlas por error.
Enter salta a la siguiente denominación, para contar sin soltar el teclado.
Verificación: parte de 38 comprobaciones de punta a punta — que la opción arranque apagada y el monto se siga escribiendo como siempre; que en efectivo aparezcan las 9 denominaciones y en Sala de Juegos las 10 fichas; que exista la ficha de $1.000.000 y no la de $2.000, que es billete; que el total y el monto se calculen solos; y que lo guardado vaya al campo que corresponde según el tipo.
Archivos: desglose.js (nuevo), app.js, supabase-api.js, index.html, styles.css, sw.js, version.js, vercel.json.
El QR que se genera en socios-comicion abre esta app con el área y el nombre ya elegidos: al socio solo le queda el PIN. Es el mismo atajo que ya hacía el acceso guardado (favAplicar), pero sirve la primera vez, cuando todavía no hay nada guardado.
?qr=<32 hex>) que se canjea contra Supabase. Ni el nombre ni el área viajan en la URL.mesas vs Mesas), así que se busca sin distinguir mayúsculas. Si el área no está en la lista, o el socio no aparece en ella, lo dice y deja elegir a mano en vez de quedarse a medias.Un problema que apareció al probarlo: en la primera visita el Service Worker se activa de inmediato (skipWaiting), toma el control y la página se recarga sola. El código ya había salido de la URL, así que la recarga se lo llevaba — y la primera visita es justo la del socio que estrena la app con su QR. Ahora el código se guarda en sessionStorage en cuanto se lee y de ahí se recupera tras la recarga; se olvida en cuanto se canjea, para no reintentar uno ya usado.
Verificación: 12 comprobaciones — que deje el área y el nombre elegidos con el PIN vacío y el aviso correcto, que el código no quede en la URL, que se canjee marcando esta app, que se olvide después de usarlo, y los tres casos que no deben romper nada: código ya usado, área que no está en la lista, y socio que no aparece en su área.
Archivos: qr-entrada.js (nuevo), app.js, index.html, sw.js, version.js, vercel.json.
zoom-in para que se note que se puede tocar.#ses-foto:not(:empty) + .ses-ico { display: none; }, para que no queden las dos cosas al lado.diarioGetSociosByArea ahora también trae foto_url; el enlace queda en el <option> del socio y se guarda en sessionStorage al entrar, igual que el nombre y el área. No hay ninguna consulta extra.index.html, styles.css, app.js, supabase-api.js, sw.js, version.js.#barraFija), en vez de fijar cada una por su lado. Así bajan juntas sin tener que calcular a mano la altura de la de arriba para posicionar la de abajo — un número que se rompería en cuanto cambie el contenido de esa franja.index.html, styles.css.pintarSesionActiva), así la barra lateral y la franja no pueden decir cosas distintas. La franja se oculta en escritorio para no duplicar, el mismo patrón que ya usa el valor por punto.index.html, styles.css, app.js.type="password" ante cualquier cambio de la página y, como ya tenía una credencial guardada para el sitio, salía a ofrecer actualizarla una y otra vez. El autocomplete="off" que ya tenía no sirve para esto: los gestores lo ignoran para el aviso de guardar.type="password"; al cerrar sesión vuelve a serlo, que es cuando de verdad se usa. Mientras usas la app no queda ningún campo de contraseña en la página, así que el navegador no tiene nada que ofrecer..campo-secreto). Se ven igual de ocultos, pero el navegador no los confunde con la clave del sitio. El único type="password" que queda es el del login, que es el que corresponde."_comentario" dentro de vercel.json. Vercel valida ese archivo contra un esquema estricto y rechaza el despliegue entero ante una propiedad desconocida. Los commits se subían bien, pero la app quedaba congelada en la versión anterior.revisar-despliegue.py a cada repositorio: comprueba que vercel.json solo use claves que Vercel acepta y que todo archivo local que carga el HTML tenga no-caché o ?v=. Hay que correrlo antes de publicar.version.js no estaba en la lista de no-caché de vercel.json y además se publicó dos veces con la misma dirección (?v=1): el navegador se quedó con la copia del primer día —la que tenía el error de mostrar la versión en espera— y nunca volvió a pedir el archivo. Por eso el número no cambiaba.version.js a las reglas de no-caché (la cabecera sola no alcanza, porque no se aplica a una copia que el navegador ya considera fresca) y se cambió la dirección a ?v=3, que es lo que la obliga a pedirla de nuevo.manifest2.json, index2.html y sw2.js en socios-comicion; manifest.json, version.js, /icons/ e /img/ en propi.solicitada. Resultado ahora: 53 recursos revisados en las cuatro apps, 0 sin protección.version.js, index.html, styles.css, sw.js.app-version, así que agregar el dato en otro lugar es poner un <span class="app-version"> y nada más.socios-comicion y Horarios comparten dominio y sin eso una mostraría la versión de la otra.version.js (nuevo), index.html, sw.js.icon-192x192.png seguía entregando los bytes antiguos aunque el archivo en el repositorio fuera otro. Y vercel.json declaraba no-cache para el HTML, el JS, el CSS y el manifiesto, pero no para las imágenes: eran justamente las que faltaban en esa lista.icono-cpn-*.png. Una URL nueva no puede tener una copia vieja en ninguna caché, así que el cambio llega sí o sí./icons/(.*) y /img/(.*) a las reglas de vercel.json, para que la próxima vez que se cambie un ícono baste con reemplazarlo.vercel.json y manifest.json son JSON válido.icons/ (renombrados), manifest.json, index.html, sw.js, vercel.json.icon-192x192.png era en realidad 1331×1331 y 866 KB; icon-512x512.png era 2048×2048 y 4,3 MB. Entre los dos, 5,2 MB para un par de íconos.apple-touch-icon ni favicon. Sin el primero, al agregar la app a la pantalla de inicio en iPhone el sistema inventa un ícono con una captura de la página.icons/ (5 archivos), manifest.json, index.html, sw.js.DIARIO:<socioId>. El prefijo separa a los usuarios del diario de los del resto del sistema —la tabla push_subscriptions es compartida con socios-comicion (ADMIN) y con propi.solicitada (el id del socio)— y el sufijo permite que el servidor excluya al autor.push-notify → versión 9. Cambio aditivo: se agregó pushDiario() y una rama para notas_recaudacion; las ramas de egresos, días PT, recaudación-al-admin y mensajes quedaron idénticas. La v8 sigue disponible para volver atrás.trg_push_nota sobre notas_recaudacion, copia del trg_push_recaudacion que ya existía.admin {sent:3, total:4} (el aviso al admin sigue funcionando igual) y diario {total:2} de 3 suscripciones — excluyó al autor. Una nota de prueba devolvió solo diario {total:2}, sin tocar al admin. Y al insertar una nota de verdad el disparador se activó solo (petición 227, HTTP 200). Las suscripciones y la nota de prueba se borraron después; quedan 8 notas y 24 recaudaciones, los mismos números de antes.sw.js (manejadores push y notificationclick), supabase-api.js (, ), , .body.dark-mode con sus variables desde antes, y nada en toda la app ponía esa clase. Ahora se usa.<head> aplica el tema guardado antes de pintar nada; si esperara a app.js, abrir en oscuro daría un destello blanco. Como en ese momento todavía no existe el <body>, las clases se ponen en el <html> (pre-oscuro / pre-negro) y app.js las reemplaza apenas arranca. También se actualiza el theme-color, para que la barra del navegador no quede blanca.app.js. Más una auditoría automática de contraste sobre todos los textos de la app: 0 elementos ilegibles en Oscuro y en Negro.index.html, styles.css, app.js.js/api.js + js/socios.js) para que los dos sistemas muestren lo mismo: solo socios activos con fecha de ingreso; un socio suma recién desde el día 15 del mes en que empiezan sus puntos; Gastos Comisión vale 1 punto fijo; se usa el puntaje guardado y, si viniera en 0, el que corresponde por antigüedad (4 de base, 2 por año, 2 en Bóveda) con el tope de su área.Promise.all de los datos del diario. Si la base de socios no responde, el diario carga igual y solo no se muestran los puntos — el bloque se oculta en vez de dejar un hueco.dbSoc que la app ya tenía para la auditoría y el login por PIN; no hay credenciales nuevas.supabase-api.js (diarioGetPuntosNomina), app.js, index.html, styles.css.Code.gs: el token y el chat_id, telegramRec(), probarTelegramRec(), el resumen diario resumenDiarioRecaudacion() (que corría con un activador de tiempo) y los 11 avisos que se disparaban en cada operación: alta, edición y borrado de recaudación, saldo, alta y borrado de nota, divisor, reinicio de datos, importación y cierre de período.Code.gs. No se tocó el front, así que el Service Worker no cambia de versión.⚠️ ACCIÓN PENDIENTE — revocar el token del bot. El token estaba escrito a mano en
Code.gs(y otro casi idéntico enpropi.solicitada/gas/code.gs). Borrarlos del archivo no los invalida: siguen en el historial de git y cualquiera con acceso al repositorio puede recuperarlos y usar el bot. Hay que entrar a @BotFather →/mybots→ el bot → API Token → Revoke, o directamente/deletebotsi ya no se va a usar. Mientras no se revoque, el token sigue vivo.Además, fuera del repositorio hay que: (1) pegar estos tres
.gsya limpios en los proyectos reales de Google Apps Script — los del repositorio son solo copias de referencia; (2) borrar el activador de tiempo deresumenDiarioRecaudacion, que si no fallará cada día al no existir la función; (3) en el proyecto GAS de socios, borrar las Script PropertiesTELEGRAM_TOKENyTELEGRAM_CHAT_ID.
cpn-marca.png es casi cuadrado (520×480): el ancho se paga casi entero en alto. En el login, 190 px de ancho medían 175 px de alto y el logo tapaba la tarjeta de acceso.opacity: .85 para que se lea como firma.styles.css (.marca-logo-login, .marca-logo-side, .marca-logo-foot). No cambia el archivo del logo ni sus colores — solo el tamaño.filter: brightness(1.45) saturate(1.05).img/marca/cpn-marca.png (520×480, 219 KB, fondo transparente). Se borran los SVG del intento anterior.styles.css?v=48, SW recaudacion-cache-v48, versión visible v48.index.html, styles.css, sw.js, img/marca/.showToast van protegidas: favAplicar() corre al arrancar y puede adelantarse a que esa función exista.app.js?v=25, SW recaudacion-cache-v44, versión visible v44.img/carlospn-logo.png) por el nuevo y «CarlosPN Interactive» pasa a «Carlos P. Nauto Interactive» en sus 4 menciones: el logo del login, el del sidebar, el del pie y la línea de texto del pie.?v=2026..marca-logo —multiply en claro, invert(1) hue-rotate(180deg) + screen en oscuro, y el mismo tratamiento forzado para el sidebar, que siempre es oscuro— funcionan igual con un PNG transparente: no hubo que tocar styles.css.recaudacion-cache-v43.index.html, sw.js, img/carlospn-logo.png.index.html (#vpMenu, #vpSticky), app.js (render), styles.css. app.js?v=24, styles.css?v=42, SW recaudacion-cache-v42, versión visible v42.styles.css (?v=41), que no lo tenía y podía quedar cacheado.index.html (botón), app.js (hist_aplicarVista, hist_toggleVista), styles.css. app.js?v=23, styles.css?v=41, SW recaudacion-cache-v41, versión visible v41.app.js (_pintarDiasFaltantes). app.js?v=22, SW recaudacion-cache-v40, versión visible v40.app.js (_diasSinIngreso). app.js?v=21, SW recaudacion-cache-v39, versión visible v39.app.js (_diasSinIngreso, _pintarDiasFaltantes). app.js?v=20, SW recaudacion-cache-v38, versión visible v38.index.html (contenedores), app.js (_diasSinIngreso, _pintarDiasFaltantes). app.js?v=19, SW recaudacion-cache-v37, versión visible v37.index.html. SW v36 (visible v36).app + socio_id). Scripts ?v=21, SW recaudacion-cache-v35, versión visible v35.#recPresenciaCard, arriba de las estadísticas): se ve en TODOS los paneles, empuja el contenido en vez de taparlo y desaparece sola cuando no hay nadie.index.html (contenedor), supabase-api.js. Scripts ?v=20, SW recaudacion-cache-v34, versión visible v34.supabase-api.js. Scripts ?v=19, SW recaudacion-cache-v33, versión visible v33.socio_id, incluida la de propi.solicitada (misma cuenta en ambas apps). Ahora se oculta solo la propia línea de esta app. Scripts ?v=18, SW recaudacion-cache-v32, versión visible v32.?v=17, SW recaudacion-cache-v31, versión visible v31.socio_id: el socio no se ve a sí mismo (SW v30)socio_id del socio en sesión, para que si tiene diario.propi y propi.solicitada abiertas a la vez no se muestre a sí mismo en la otra app. Scripts ?v=16, SW recaudacion-cache-v30, versión visible v30.pagehide borraba la presencia al apagar la pantalla o cambiar de app. Ya no se borra en segundo plano; al volver al frente se re-marca y re-lee. Vigencia ampliada a 3 minutos.?v=15, SW recaudacion-cache-v29.rec_presencia (SW v27)rec_presencia (latido cada 20s) y todas las apps la leen cada 5s → el nombre aparece siempre, sin depender del websocket. Al salir se borra la fila. Scripts ?v=14, SW recaudacion-cache-v27.join/leave además de sync, repintado de respaldo cada 4s, render al confirmar suscripción, window.recPresRender expuesto. Scripts ?v=13, SW recaudacion-cache-v26.ch.track se enviaba antes de que el canal rec-presencia estuviera suscrito y se perdía en silencio. En diario era sistemático: el panel "Agregar" es el inicial, así que la marca salía justo al entrar (canal aún conectándose) → propi.solicitada nunca veía al socio.SUBSCRIBED (y en cada reconexión). Scripts ?v=12, SW recaudacion-cache-v25.rec-presencia (proyecto REC). Se marca presencia al entrar al panel agregarPanel (y al cambiar el tipo), se quita al salir/cerrar sesión, y apiAddRecaudacion emite el aviso de "agregó". Banner y toast autocontenidos.supabase-api.js (módulo + aviso al agregar), app.js (switchPanel/tipo/login/logout). SW recaudacion-cache-v24, scripts ?v=11.diario.propi ya no se leen ni se comparan en el navegador.diario_pins se cerró al rol anon (RLS activo, sin políticas permisivas).pin-auth (service_role):
diarioStatus (¿tiene PIN?), diarioVerify (validar), diarioSet (crear/cambiar).recaudacion-cache-v23; supabase-api.js/app.js → ?v=10.diarioSuscribirPushdiarioPedirPermisoPushapp.jsindex.html