
Chismes del Lince 15: La Caja de las Pastillas, un recordatorio
Noche cerrada en el claro del pinsapo. El Lince se rasca la oreja con cara de duda.
—Gata, ¿me he tomado ya lo de la cena o eso fue ayer?
—No lo sé, Lince. Y tú tampoco.
—Pues a ver cómo se arregla eso.
La Gata sonríe: —Con una caja que no sabe nada de medicinas. No te dice qué tomar ni cuánto: solo te avisa a la hora que le han dicho y apunta que has pulsado «Ya está». Del resto se encarga tu médico.
—¿Y cómo suena si el móvil está bloqueado?
—Ahí está el truco. Una web no puede sonar con el móvil bloqueado. Pero el calendario sí.
Introducción
El móvil ya había escuchado, hablado, contado pasos, medido la voz y la mano, usado la pantalla como tambor y como papel. Ahora le pedimos algo mucho más humilde y mucho más difícil: que no se nos olvide algo.
El Chisme 15 nace de dos ideas de nuestra lista que acabaron siendo la misma: una caja de recordatorios por franjas (desayuno, comida, cena, noche) y un pastillero con alarma que usa el calendario del móvil. Las hemos juntado en una sola webapp con tres piezas:
- Un horario que un familiar escribe en el propio móvil de la persona.
- Un archivo
.icsque mete los avisos en Google Calendar, que sí suena aunque el móvil esté bloqueado. - Una página a la que se llega desde el aviso, con un botón enorme («YA ESTÁ») y un resumen del día que se puede mandar por WhatsApp.
Es, con diferencia, lo más cercano a un producto sanitario de toda la lista. Por eso es también la que más límites se pone, y los ponemos desde el principio.
Reglas de diseño, las de siempre: botones enormes, nada de teclear para la persona (el que escribe es el familiar, una vez), letra grande y todo lo que dice la Gata, escrito.
⚠️ Esto es un recordatorio, no un sistema médico
No sabe qué medicinas existen, no da pautas, no sugiere dosis y no cambia nada. Solo repite lo que un familiar escribió, a la hora que escribió. Y «YA ESTÁ» solo prueba que alguien pulsó un botón, no que la pastilla se tomara.
No usa IA ni Worker. El horario y el registro de confirmaciones viven en el móvil, en el navegador. Hay una excepción que explicamos más abajo: el propio calendario se sincroniza con la cuenta de Google.
Para las pruebas usamos solo nombres inventados («pastilla blanca (ejemplo)»). Una lista de medicación real es un dato de salud.
Pruébala aquí (en su propia dirección, en el móvil): https://montesteam.org/app/pastillero.html

Cómo funciona por dentro
Familiar → formulario → localStorage + .ics → Google Calendar → aviso → toque → página ?f=franja → «YA ESTÁ» → registro → resumen → WhatsApp
- El familiar abre la página en el móvil de la persona, pulsa «Crear horario», marca las franjas, escribe las pastillas de cada una (hay un botón de pastillas de muestra) y elige 7 o 14 días.
- Pulsa «Guardar y crear calendario». El horario se guarda en el navegador y se descarga
pastillero.ics. - Se abre el
.icsy se elige Calendario de Google. Los avisos quedan puestos. - A la hora de cada franja suena el aviso. Se toca, se abre el evento y se toca el enlace: la página enseña esa franja, con sus pastillas y el botón grande.
- Al pulsar «YA ESTÁ» se guarda la hora de la confirmación. Si no se pulsa, se queda en «pendiente» y no insiste ni avisa a nadie.
- La pantalla de inicio enseña el resumen de hoy y un botón para enviarlo por WhatsApp.
Seis decisiones de ingeniería
1. El calendario avisa, la web registra. Una web no puede despertar el móvil: sin servidor, no hay forma de programar un aviso que suene con la pantalla bloqueada. Un calendario sí. Así que repartimos el trabajo: el calendario hace lo que la web no puede (avisar a su hora) y la web hace lo que el calendario no sabe (hablar, enseñar las pastillas y apuntar la confirmación).
2. Eventos sueltos y con caducidad, no repetidos para siempre. Generamos un evento por día y franja, solo para 7 o 14 días. Si el horario cambia, el viejo caduca solo y el familiar crea uno nuevo. Cada evento tiene un identificador fijo (UID), pensado para que, al volver a importar, el calendario actualice en vez de duplicar. Eso hay que comprobarlo (más abajo).
3. Ningún nombre de pastilla va al calendario. El evento solo dice «💊 Pastillero – Desayuno» y lleva el enlace. Los nombres de las pastillas se quedan en el móvil, en la página.
4. Todo local. localStorage guarda el horario y el registro. No hay cuentas, ni servidor, ni base de datos. Es seguro por diseño, a cambio de que el registro no se vea desde otro móvil.
5. La voz, opcional y apagada. Que la página hable («Son las ocho. Tienes un recordatorio pendiente») usa la voz del propio móvil. Que escuche el «Ya está» usa el reconocimiento de voz de Chrome, que envía el audio a Google. Por eso viene desactivado: hay que marcarlo a propósito, y el botón grande siempre funciona.
6. El resumen lo manda la persona, y sin nombres. El texto de WhatsApp dice «Desayuno: tomada a las 08:12 / Comida: pendiente», sin nombres de medicinas. Lo envía quien tiene el móvil, pulsando un botón: la aplicación no manda nada sola.
Qué es un .ics
Un .ics es un archivo de texto con un formato estándar de calendario. Cada aviso es un bloque VEVENT; esto es uno de los que genera la aplicación (el archivo real parte las líneas largas para cumplir el estándar):
BEGIN:VEVENT
UID:pastillero-20261002-desayuno@montesteam.org
DTSTAMP:20261001T181500Z
DTSTART:20261002T080000
DTEND:20261002T081500
SUMMARY:💊 Pastillero - Desayuno
DESCRIPTION:Toca el enlace para confirmar: https://montesteam.org/app/pastillero.html?f=desayuno\nSolo es un recordatorio.
URL:https://montesteam.org/app/pastillero.html?f=desayuno
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Pastillero
TRIGGER:PT0M
END:VALARM
END:VEVENT
UIDes el identificador: mismoUID, mismo evento.DTSTARTno llevaZni zona horaria: es una hora «flotante», y el calendario la entiende en la hora local del móvil.VALARMes el aviso.TRIGGER:PT0Msignifica «a la hora en punto». (-PT10Msería «10 minutos antes».)
¿Dónde se guardan las alarmas y los avisos?
En Google Calendar, dentro de la cuenta de Google del móvil. No son correos en Gmail: son eventos del calendario. Ojo con dos cosas:
- La app de Google Calendar del móvil, que sepamos, no deja crear calendarios nuevos (eso se hace desde un ordenador). Por eso no dependemos de un calendario «Pastillero» aparte: todos los eventos empiezan por «💊 Pastillero», y se encuentran buscando esa palabra.
- Google Calendar sincroniza los eventos con la cuenta de Google. Eso significa que la franja y la hora de cada aviso sí viajan a la nube de Google. Los nombres de las pastillas, no.
Los límites: qué da de sí esta caja
- Es un recordatorio, no un control. «YA ESTÁ» es un clic. La aplicación no puede saber si la pastilla se tomó, ni si era la correcta.
- No sabe de medicinas. No hay base de datos de fármacos, ni dosis, ni interacciones, ni avisos. A propósito.
- Son dos toques, no uno. Probablemente haga falta tocar el aviso y luego el enlace dentro del evento. Hay que comprobarlo en el móvil real.
- El sonido depende del móvil. «No molestar», el volumen de notificaciones y el ahorro de batería pueden retrasar o silenciar un aviso. Es el calendario quien manda, no nosotros.
- El registro vive en el navegador. Si se borran los datos del navegador, se pierde. Y un móvil compartido comparte el registro.
- El resumen solo lo ve quien lo envía. Un familiar no puede consultarlo por su cuenta. Es decisión nuestra: preferimos que no haya datos en ningún servidor.
- Cambiar el horario no borra los avisos viejos del calendario. Hay que borrarlos a mano o dejar que caduquen.
- La confirmación por voz depende de Chrome y de Internet, y envía el audio a Google. Por eso está apagada.
Lo que todavía hay que comprobar en el móvil real
Esto no lo hemos probado todavía, y lo decimos tal cual:
- ¿Qué hace el móvil al abrir el
.ics? ¿Ofrece Google Calendar o pregunta con qué abrirlo? - ¿Suena el aviso con el móvil bloqueado? ¿Y con el ahorro de batería activado?
- ¿Cuántos toques hacen falta desde el aviso hasta el botón «YA ESTÁ»?
- Al volver a importar un horario cambiado, ¿el calendario sustituye los eventos o los duplica?
Para el aula
- Abre un
.icscon el bloc de notas. Es texto. Cambia la hora de un evento y vuelve a importarlo. ¿Qué pasa si no cambias elUID? ¿Y si lo cambias? - Cambia el aviso. Pon
TRIGGER:-PT10My mira cuándo suena. ¿Qué ventajas y qué inconvenientes tiene avisar antes de la hora? - Hora flotante contra hora con zona. Investiga qué diferencia hay entre
DTSTART:20261002T080000yDTSTART:20261002T080000Z. ¿Qué pasaría si viajas? - Mira el almacenamiento. En el navegador de un ordenador, abre las herramientas de desarrollo y busca
pastillero.cfgypastillero.log. ¿Qué datos hay? ¿Quién podría leerlos en un móvil compartido? - Añade una franja. En el código, la lista
Fdefine las franjas y sus horas por defecto. Añade «Merienda» y comprueba qué más hay que tocar. - Cambia los 7 y 14 días. ¿Qué pasa con 30? ¿Por qué elegimos pocos?
- Diseña una alternativa con servidor. ¿Qué haría falta para que el familiar viera el resumen en su móvil? ¿Qué riesgos nuevos aparecen? Dibuja el esquema.
- Piensa en la ética. Si el resumen dice «pendiente», ¿puede alguien sentirse vigilado o culpable? ¿Quién debería ver este registro? ¿Qué diferencia hay entre recordar y controlar?
Del aula a la calle: Aprendizaje-Servicio
La ruta de siempre: aprender → escuchar → construir y probar con acompañamiento → mejorar → devolver. Queremos ofrecérsela a Parkinson Sol Estepona y preguntar, sobre todo, a profesionales y a familias si les sirve de algo. Puede que no, y también aprenderemos. Ya existen aplicaciones de recordatorios: queremos saber qué aportaría esta que no tengan las demás, y qué sobra.
Lo que queremos aprender de las personas
- ¿Se entiende el aviso? ¿Se oye? ¿Se ve bien la letra?
- ¿Es cómodo el paso de tocar el aviso a tocar el enlace? ¿Hay alguna forma de ahorrar un toque?
- ¿Quién escribe el horario en la práctica? ¿Es razonable que lo haga el familiar en el móvil de la persona?
- ¿Se entiende que «YA ESTÁ» es solo un apunte?
- ¿Genera sensación de control o de culpa ver «pendiente»?
- ¿Qué echarías de menos en el resumen? ¿Y qué te sobra?
- ¿Prefieres que el resumen lo vea el familiar sin tener que pedirlo? ¿A cambio de qué?
Si alguien se siente incómodo, se para.
Paso 1: prepara la página
Este chisme no necesita Worker ni clave de API. Copia el código de la sección HTML y guárdalo como pastillero.html en tu carpeta app. Lo único que quizá quieras tocar está al principio del <script>:
const F = [["desayuno","Desayuno","08:00"], ["comida","Comida","14:00"],
["cena","Cena","21:00"], ["noche","Noche","23:00"]]; // franjas y horas por defecto
Y dentro de la función ics(): la duración del evento (15 minutos, hm[1]+15) y el aviso (TRIGGER:PT0M, a la hora en punto).
Paso 2: súbela y ábrela
Ábrela directamente en su dirección (no dentro de una entrada de WordPress), desde el móvil y con https. Genera el .ics desde la página ya publicada: los enlaces de los avisos usan la dirección desde la que se creó.
Paso 3: pruébala
- Abre la página en el móvil y pulsa «Crear horario (familiar)».
- Pulsa «Rellenar con pastillas de muestra». Pon una franja a cinco minutos de ahora para no esperar.
- Elige 7 días y pulsa «Guardar y crear calendario».
- Abre
pastillero.icsdesde la notificación de descarga y elige Calendario de Google. - Busca «Pastillero» en el calendario y comprueba que están los eventos.
- Espera al aviso (con el móvil bloqueado). Tócalo, abre el enlace y pulsa «YA ESTÁ».
- Vuelve al inicio, mira el resumen y prueba «Enviar resumen por WhatsApp».
Si algo falla
- El
.icsno se abre o no ofrece Google Calendar: ábrelo desde el gestor de archivos, o desde Calendario en «Importar». Cuéntanos qué modelo es. - No suena con el móvil bloqueado: revisa «No molestar», el volumen de notificaciones y el ahorro de batería para Calendario.
- No encuentras los eventos: busca «Pastillero». Pueden haberse guardado en otro calendario de la cuenta.
- El enlace abre la pantalla de inicio y no la franja: la página solo abre una franja si el horario está guardado en ese mismo navegador. Abre el enlace con el mismo Chrome con el que se creó.
- El registro desaparece: vive en el navegador. Si se borran los datos o se usa el modo incógnito, se pierde.
- La voz no escucha: el navegador puede no tener reconocimiento de voz. El botón grande siempre funciona.
- Aparecen eventos duplicados: al cambiar el horario, borra a mano los antiguos buscando «Pastillero», o deja que caduquen.
Conclusión
Con un formulario, localStorage, un archivo .ics, Google Calendar y una página con un botón enorme hemos hecho un recordatorio de pastillas que no sabe nada de medicinas: familiar → horario local → .ics → calendario → aviso → toque → página → «YA ESTÁ» → registro → resumen. Lo interesante, otra vez, son las decisiones: repartir el trabajo entre el calendario y la web, no meter nombres de pastillas en el calendario, dejar la voz apagada por defecto, no usar servidor ni IA, decir «pendiente» y no insistir y, sobre todo, no vender como control lo que solo es un recordatorio.
El Lince ya sabe que lo de la cena fue ayer. La Gata, que lo apuntó, ya está pensando en el chisme 16.
⚠️ ANTES DE PUBLICAR NADA, LEE ESTO
No es un producto sanitario. Es una prueba técnica de un centro educativo. No la presentes como herramienta para gestionar, supervisar o mejorar un tratamiento, ni prometas beneficios que no hemos comprobado. No decide ni sugiere medicación, dosis ni horarios.
Una lista de medicación es un dato de salud. Para las pruebas, usa solo nombres inventados. Si alguien quiere usarla de verdad, pide consentimiento a la persona (y a su familia) y explícale dónde se guardan los datos y cómo se borran. Los nombres de las pastillas no salen del móvil, pero la hora y la franja de cada aviso se sincronizan con la cuenta de Google del calendario.
«YA ESTÁ» no demuestra nada. Solo registra que se pulsó un botón. Que ponga «pendiente» no quiere decir que no se haya tomado. No saques conclusiones ni reproches a nadie por el resumen.
Un móvil compartido comparte el registro. Borra los datos del navegador al terminar una sesión de prueba.
Las alarmas dependen del móvil. No debe ser el único recurso de nadie. Si un olvido puede tener consecuencias, hacen falta otras medidas, y eso lo decide la persona con su médico o su farmacéutico.
Si lo usas en un centro educativo, revisa las indicaciones de la persona responsable de protección de datos.
La tecnología es la parte fácil. Decidir cómo, con quién y para qué la usamos es el verdadero chisme.
HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>La caja de las pastillas</title>
<style>
:root{--ink:#1d1715;--bg:#f8f6f3;--bermejo:#a3261a;--ok:#17703a;--line:#ddd5cd;--mute:#5f524b}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:20px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
main{max-width:560px;margin:0 auto;padding:18px 16px 40px}
h1{font-size:30px;line-height:1.15;margin:.3em 0 .4em;color:var(--bermejo);font-weight:800}
h2{font-size:22px;margin:0 0 .4em}
button,input,textarea,select{font:inherit;color:inherit}
:focus-visible{outline:4px solid #1b5fd1;outline-offset:2px}
.btn{display:block;width:100%;min-height:64px;margin:12px 0;border:0;border-radius:14px;background:var(--bermejo);color:#fff;font-weight:700;cursor:pointer}
.btn.sec{background:#fff;color:var(--bermejo);border:2px solid var(--bermejo)}
.btn.ok{background:var(--ok);min-height:150px;font-size:36px;border-radius:24px}
.box{background:#fff;border:1px solid var(--line);border-radius:10px;padding:14px 16px;margin:14px 0}
.line{padding:8px 0;border-bottom:1px solid var(--line)}
.line:last-child{border-bottom:0}
label{display:block;font-weight:600;margin:.4em 0 .2em}
input[type=time],input[type=date],select,textarea{width:100%;padding:10px;border:2px solid var(--line);border-radius:8px;background:#fff}
textarea{min-height:76px;margin-top:8px}
.row{display:flex;gap:12px;align-items:center}
.row>*{flex:1}
.row input[type=checkbox]{flex:0 0 28px;width:28px;height:28px}
.note{font-size:16px;color:var(--mute)}
.done{color:var(--ok);font-weight:700}
</style>
</head>
<body>
<main id="app"></main>
<script>
const F=[["desayuno","Desayuno","08:00"],["comida","Comida","14:00"],["cena","Cena","21:00"],["noche","Noche","23:00"]];
const $=s=>document.querySelector(s);
const pad=n=>String(n).padStart(2,"0");
const ymd=d=>d.getFullYear()+"-"+pad(d.getMonth()+1)+"-"+pad(d.getDate());
const esc=s=>s.replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));
const ls={get(k,d){try{return JSON.parse(localStorage.getItem(k))||d}catch(e){return d}},set(k,v){try{localStorage.setItem(k,JSON.stringify(v))}catch(e){}}};
let cfg=ls.get("pastillero.cfg",null),log=ls.get("pastillero.log",{});
const BASE=location.protocol.startsWith("http")?location.origin+location.pathname:"https://montesteam.org/app/pastillero.html";
const show=h=>{$("#app").innerHTML=h;window.scrollTo(0,0)};
const foot=()=>'<p class="note">Solo es un recordatorio. No sustituye lo que te haya indicado tu médico o tu farmacéutico. Los datos de las pastillas se guardan únicamente en este móvil.</p>';
const stat=(d,x)=>{const t=(log[d]||{})[x.k];return t?'<span class="done">Tomada a las '+t+'</span>':"Pendiente"};
function inicio(){history.replaceState(null,"",location.pathname);home()}
function home(){
const d=ymd(new Date());
let h='<h1>La caja de las pastillas</h1><p class="note">Un recordatorio, nada más. No decide medicación ni cambia dosis.</p>';
if(!cfg){h+='<div class="box">Todavía no hay horario. Un familiar puede crearlo en este móvil con «Crear horario».</div>'}
else{
h+='<div class="box"><h2>Hoy</h2>'+cfg.fr.filter(x=>x.on).map(x=>'<div class="line">'+x.n+' ('+x.h+'): '+stat(d,x)+'</div>').join("")+'</div>';
h+='<button class="btn" onclick="wa()">Enviar resumen por WhatsApp</button>';
}
h+='<button class="btn sec" onclick="conf()">Crear horario (familiar)</button>'+foot();
show(h);
}
function wa(){
const d=ymd(new Date());
const t="Pastillero, "+new Date().toLocaleDateString("es-ES",{weekday:"long",day:"numeric",month:"long"})+"\n"+
cfg.fr.filter(x=>x.on).map(x=>{const c=(log[d]||{})[x.k];return x.n+": "+(c?"tomada a las "+c:"pendiente")}).join("\n");
window.open("https://wa.me/?text="+encodeURIComponent(t),"_blank");
}
function rec(k){
const x=cfg&&cfg.fr.find(y=>y.k===k&&y.on);
if(!x){inicio();return}
const d=ymd(new Date()),t=(log[d]||{})[k];
const ps=x.p.split("\n").map(s=>s.trim()).filter(Boolean);
show('<h1>'+x.n+'</h1><div class="box">'+(ps.map(p=>'<div class="line">'+esc(p)+'</div>').join("")||"No hay pastillas anotadas.")+'</div>'+
(t?'<div class="box done">Confirmado a las '+t+'</div>':'<button class="btn ok" onclick="ok(\''+k+'\')">YA ESTÁ</button>')+
'<button class="btn sec" onclick="hablar(\''+k+'\')">Escuchar el recordatorio</button>'+
'<div class="box"><label class="row"><input type="checkbox" id="vz" onchange="voz(\''+k+'\',this.checked)"><span>Confirmar diciendo «Ya está»</span></label><p class="note">Ojo: el reconocimiento de voz de Chrome envía el audio a Google. Por eso viene desactivado.</p></div>'+
'<button class="btn sec" onclick="inicio()">Ver el resumen de hoy</button>'+foot());
}
function ok(k){
const n=new Date(),d=ymd(n);
log[d]=log[d]||{};log[d][k]=pad(n.getHours())+":"+pad(n.getMinutes());
ls.set("pastillero.log",log);
dec("Anotado. Muy bien.");
rec(k);
}
function dec(s){
if(!window.speechSynthesis)return;
speechSynthesis.cancel();
const u=new SpeechSynthesisUtterance(s);u.lang="es-ES";u.rate=.9;speechSynthesis.speak(u);
}
function hablar(k){
const x=cfg.fr.find(y=>y.k===k),n=new Date(),h=n.getHours()%12||12,m=n.getMinutes();
const hecho=(log[ymd(n)]||{})[k];
if(hecho){dec("Este recordatorio ya está anotado.");return}
dec((h===1?"Es la una":"Son las "+h)+(m?" y "+m:" en punto")+". Tienes un recordatorio pendiente: "+x.n.toLowerCase()+".");
}
let rc;
function voz(k,on){
const S=window.SpeechRecognition||window.webkitSpeechRecognition;
if(!on){if(rc)rc.stop();return}
if(!S){alert("Este navegador no sabe escuchar. Usa el botón grande.");$("#vz").checked=false;return}
rc=new S();rc.lang="es-ES";rc.continuous=true;
rc.onresult=e=>{const s=e.results[e.results.length-1][0].transcript.toLowerCase();if(/ya est|ya me|hecho|listo|tomad/.test(s)){rc.stop();ok(k)}};
rc.start();
}
function conf(){
const c=cfg||{dias:7,ini:ymd(new Date()),fr:F.map(a=>({k:a[0],n:a[1],h:a[2],on:true,p:""}))};
show('<h1>Crear horario</h1><p class="note">Escribid solo nombres neutros, como «pastilla blanca». Nada de dosis ni pautas. Todo se guarda en este móvil.</p>'+
c.fr.map((x,i)=>'<div class="box"><label class="row"><input type="checkbox" id="on'+i+'" '+(x.on?"checked":"")+'><span>'+x.n+'</span><input type="time" id="h'+i+'" value="'+x.h+'"></label><textarea id="p'+i+'" placeholder="Una por línea. Ejemplo: Pastilla blanca (ejemplo)">'+esc(x.p)+'</textarea></div>').join("")+
'<div class="box row"><div><label for="dias">Días</label><select id="dias"><option '+(c.dias==7?"selected":"")+'>7</option><option '+(c.dias==14?"selected":"")+'>14</option></select></div><div><label for="ini">Desde</label><input type="date" id="ini" value="'+c.ini+'"></div></div>'+
'<button class="btn sec" onclick="muestra()">Rellenar con pastillas de muestra</button>'+
'<button class="btn" onclick="guardar()">Guardar y crear calendario</button>'+
'<button class="btn sec" onclick="inicio()">Volver</button>'+foot());
}
function muestra(){
["Pastilla blanca (ejemplo)\nGotas de ejemplo","Pastilla A (ejemplo)","Pastilla redonda azul (ejemplo)\nPastilla B (ejemplo)","Pastilla pequeña (ejemplo)"].forEach((t,i)=>$("#p"+i).value=t);
}
function guardar(){
cfg={dias:+$("#dias").value,ini:$("#ini").value,fr:F.map((a,i)=>({k:a[0],n:a[1],h:$("#h"+i).value||a[2],on:$("#on"+i).checked,p:$("#p"+i).value}))};
ls.set("pastillero.cfg",cfg);
ics();
show('<h1>Horario guardado</h1><div class="box"><p>Se ha descargado <b>pastillero.ics</b>. Ábrelo desde la notificación de descarga y elige <b>Calendario de Google</b> para añadir los avisos.</p></div><button class="btn sec" onclick="ics()">Descargar otra vez</button><button class="btn" onclick="inicio()">Ir al inicio</button>'+foot());
}
function fold(s){const o=[];while(s.length>60){o.push(s.slice(0,60));s=" "+s.slice(60)}o.push(s);return o.join("\r\n")}
function ics(){
const L=["BEGIN:VCALENDAR","VERSION:2.0","PRODID:-//MonteSTeam//Pastillero//ES","CALSCALE:GREGORIAN","X-WR-CALNAME:Pastillero"];
const now=new Date().toISOString().replace(/[-:]/g,"").slice(0,15)+"Z";
const p=cfg.ini.split("-").map(Number);
for(let i=0;i<cfg.dias;i++){
const d=new Date(p[0],p[1]-1,p[2]+i),ds=d.getFullYear()+pad(d.getMonth()+1)+pad(d.getDate());
cfg.fr.filter(x=>x.on).forEach(x=>{
const hm=x.h.split(":").map(Number),e=new Date(d.getFullYear(),d.getMonth(),d.getDate(),hm[0],hm[1]+15);
const fin=e.getFullYear()+pad(e.getMonth()+1)+pad(e.getDate())+"T"+pad(e.getHours())+pad(e.getMinutes())+"00";
const link=BASE+"?f="+x.k;
L.push("BEGIN:VEVENT","UID:pastillero-"+ds+"-"+x.k+"@montesteam.org","DTSTAMP:"+now,
"DTSTART:"+ds+"T"+x.h.replace(":","")+"00","DTEND:"+fin,
"SUMMARY:💊 Pastillero - "+x.n,
"DESCRIPTION:Toca el enlace para confirmar: "+link+"\\nSolo es un recordatorio.",
"URL:"+link,"BEGIN:VALARM","ACTION:DISPLAY","DESCRIPTION:Pastillero","TRIGGER:PT0M","END:VALARM","END:VEVENT");
});
}
L.push("END:VCALENDAR");
const b=new Blob([L.map(fold).join("\r\n")+"\r\n"],{type:"text/calendar;charset=utf-8"});
const a=document.createElement("a");a.href=URL.createObjectURL(b);a.download="pastillero.ics";
document.body.appendChild(a);a.click();a.remove();
}
const f=new URLSearchParams(location.search).get("f");
if(f&&cfg)rec(f);else home();
</script>
</body>
</html>



