Historial de cambios deployados a producción en cert.pentairlatam.com.
Formato basado en Keep a Changelog y versionado SemVer. Cada versión corresponde a un grupo coherente de cambios; los hashes refieren al commit principal de cada release.
v1.18.0 — 2026-06-03
Agregado
- Logo Pentair Pool en el email del certificado (PLE-1734). El header del email ahora muestra el logo oficial en lugar del texto "PENTAIR POOL LATAM" en mayúsculas. Se exporta a PNG 480px de ancho (display ~160px) para máxima compatibilidad con clientes de email (Gmail, Outlook suelen romper SVG). Se sirve desde
cert.pentairlatam.com/pentair-pool-primary-email.png. scripts/build-email-logo.ts— script de un solo uso que regenera el PNG desde el SVG fuente consharpa 300dpi. Correr cuando cambie el logo:pnpm tsx scripts/build-email-logo.ts.
v1.17.0 — 2026-05-26
Agregado
- Filtros y orden en el listado "Todos los eventos" del admin. Nuevo componente client
EventosTable:- Filtro por status con pills y conteo: Todos / Borrador / Abierto / Cerrado / Enviado.
- Búsqueda por nombre o ciudad (instantánea, con botón de limpiar).
- Orden configurable: Fecha reciente (default) / Fecha antigua / Nombre A-Z / Asistencia.
- Estado vacío cuando ningún evento coincide con el filtro.
- El filtrado/orden es 100% client-side (instantáneo) sobre los eventos que el server ya trae ordenados por fecha desc. Pensado para el volumen actual (decenas de eventos).
Cambiado
- El render de la tabla de eventos se movió de
page.tsx(server) aEventosTable.tsx(client). El server sigue calculando la asistencia (tally) y la pasa ya mergeada.
v1.16.0 — 2026-05-26
Cambiado
- El certificado se envía por correo al instante en el check-in (PLE-1677). Antes el email quedaba diferido hasta el cron diario (a la hora configurada); ahora
checkinActionlo manda de inmediato tras generar el cert, reutilizando los buffers ya en memoria (no re-descarga de Storage). Esto cumple lo pedido por Bobe: "lo antes posible, al momento del submission". Solo aplica si el evento tieneusar_emailactivo. Con luz verde de Resend confirmada en la junta del 26-may. - El cron diario pasa a ser red de seguridad / reintento. Como ya filtra
enviado_at IS NULL, ignora automáticamente los que se enviaron al instante y solo procesa los que fallaron en el submit (quedan conenviado_atnull yemail_status: submit-send-error: ...). No se tocó el cron.
Notas
- Si el envío inmediato falla, no rompe el check-in: el asistente ya tiene su descarga instantánea (PDF + JPG); el error se registra en
email_statusy el cron reintenta. El email es backup, no critical path. - El check-in tarda ~0.5-1s más por el envío síncrono del correo, aceptable dado que ya esperaba render + upload.
v1.15.0 — 2026-05-26
Agregado
- Navegación móvil del admin con hamburguesa + drawer (PLE-1676). En celular, el header ahora muestra logo + botón hamburguesa; al tocarlo abre un drawer lateral con los items con label (Eventos, Base de datos, Usuarios, Configuración), info del usuario y botón Salir. Reemplaza el nav icon-only de v1.14.1, que era ambiguo (íconos sin texto). El drawer cierra al tocar un link, al tocar el backdrop o con Escape; bloquea el scroll del body mientras está abierto y marca el item activo.
app/(admin)/admin/nav-items.ts— fuente única de los items del nav, compartida entre el nav de desktop (server) y el drawer móvil (client) para no duplicar la lista.
Cambiado
- Nav de desktop (
≥640px) sin cambios visuales: sigue mostrando los labels completos en la barra superior. Ahora se renderiza mapeandoadminNavItemsen vez de tener cada link hardcodeado.
v1.14.1 — 2026-05-26
Arreglado
- Pantalla de login se desbordaba horizontalmente en celular (PLE-1676). El
<Footer>estaba dentro del<main className="flex ... justify-center">(flex-row), volviéndose hermano horizontal del card de login: empujaba el card a la izquierda del centro y su contenedormax-w-6xlforzaba un ancho que rompía el viewport móvil. Fix: el<Footer>ahora es hermano del<main>(bajo el<body>flex-col), donde sumt-autolo manda al fondo a ancho completo y el card queda centrado correctamente. El<main>se envolvió en un fragment con el footer afuera. - Nav del admin con riesgo de overflow en celular (PLE-1676, preventivo). Los 4 links del header (Eventos, Base de datos, Usuarios, Configuración) mostraban su label completo en móvil; "Base de datos" + "Configuración" podían desbordar en pantallas angostas. Ahora los labels son icon-only en móvil (
hidden sm:inline) conaria-labelpara accesibilidad, consistente con el patrón que ya usaba el botón "Salir".
v1.14.0 — 2026-05-26
Tres fixes derivados de la junta Sabi + Bobe + Alex del 2026-05-26, pre-primer seminario real (CDMX 2-jun "Hidráulica y Calentamiento"). Objetivo: cerrar gaps que Sabi observó en vivo y endurecer el flujo antes de presentárselo a Ana Cris.
Agregado
<SubmitButton>reutilizable (components/SubmitButton.tsx) — usauseFormStatusde React 19 para deshabilitarse automáticamente mientras la server action está pending. Muestra spinnerLoader2+ label custom ("Creando evento...","Guardando...").- Helper
lib/filename.tsconslugifyForFilename()ycertificateFilename(). Genera nombres de archivo title-case ASCII-safe preservando acrónimos (UV, VSF, HP) y CamelCase (IntelliFlo3, MasterTemp). Reutilizable en futuras descargas (encuesta, exports, etc.).
Cambiado
- Filename de descarga del cert ahora incluye tema + nombre del asistente en title-case (PLE-1730 + PLE-1735). Antes:
certificado-pentair-pool.pdfpara todos. Ahora:Certificado-Hidraulica-Y-Calentamiento-Maria-Fernanda-Ledezma.pdf. Aplica también al.jpg. Pedido de Sabi: "capitales al inicio", "muy informal cuando se descarga en letras bajas".subirCertificado()acepta opcionalmente{ seminario, asistente }; sin el meta cae al filename legacy. crearEventoActionyactualizarEventoActionahora usan<SubmitButton>en lugar de<button type="submit">plano. El botón se deshabilita en cuanto se hace click y se re-habilita solo si la action falla.
Arreglado
- Botón "Crear evento" se podía clickear varias veces y generaba duplicados (PLE-1731). Sabi reprodujo el bug en vivo en la junta del 2026-05-26 — hizo doble clic al crear el evento de prueba CDMX. Fix: ahora el botón se deshabilita en cuanto se hace click y muestra "Creando evento..." con spinner hasta que la server action termina. Mismo fix aplicado a "Guardar cambios" del editar.
- Una sola submission por email por evento (PLE-1732). Si el mismo email ya generó certificado en un evento (
cert_jpg_urlycert_pdf_urlya guardados),checkinActionretorna las URLs existentes en vez de re-renderizar y sobrescribir. Anti-abuso del QR compartido por WhatsApp por la misma persona — la primera submission "queda" y las subsiguientes son no-op. La encuesta no se sobrescribe.
v1.13.4 — 2026-05-21
Arreglado
- QR del admin se veía pixelado/borroso: el
image-rendering: crisp-edgesque se había agregado en v1.11.4 (cuando se intentaba culpar al downscale del browser por las "rayas") forzaba nearest-neighbor scaling, que se ve fatal al hacer downscale 2014→250 px (cada pixel display representaría ~64 pixeles fuente, perdiendo info y creando aliasing del logo). Removido elimageRendering— el bilinear default del browser produce un QR limpio sin alias, y como en v1.11.5 ya solucionamos las "rayas" de raíz alineando el frame a la grilla de módulos, ya no necesitamos forzar pixel-perfect rendering.
v1.13.3 — 2026-05-21
Cambiado
- Pesos tipográficos del cert refinados según especificación visual:
- Subtítulo "RECONOCEMOS LA PARTICIPACIÓN DE:": SemiBold → Medium (500).
- Nombre del asistente: Bold (700) — sin cambio, ya estaba.
- Tema dentro de la pill: Bold → SemiBold (600).
- Fecha + ciudad: SemiBold → Medium (500).
- Nombre del firmante: Bold → SemiBold (600).
- Líneas decorativas más delgadas y anchas (alineación visual con el aprobado):
- Línea cyan debajo del nombre: 280×8 → 360×4.
- Líneas cyan arriba de las firmas: 460×6 → 540×3.
- Agregado peso Medium (500) a la familia Barlow tanto en satori (TTF bundled) como en next/font/google para el sistema.
v1.13.2 — 2026-05-20
Arreglado
- Saltos de línea del párrafo de reconocimiento: el auto-wrap de satori partía el párrafo donde le acomodaba según el ancho disponible (después de "y", "piscinas y"), pero el ejemplo aprobado por Pentair tiene un corte específico (después de "desarrollo"). Forzados dos
<div>separados con el texto exacto de cada línea para garantizar el quiebre como en el aprobado.
v1.13.1 — 2026-05-20
Cambiado
- Tamaños de letra calibrados contra el cert aprobado (PLE-1462). El cert anterior se veía modesto comparado con la versión de Rubén; ahora el nombre del asistente y demás elementos tienen el peso visual del ejemplo aprobado.
- Nombre asistente (1 línea): 150 → 190 px máximo (escala según largo: 95 / 130 / 160 / 190).
- Subtítulo "RECONOCEMOS LA PARTICIPACIÓN DE:": 38 → 44 px.
- Frase "Por completar satisfactoriamente...": 34 → 38 px.
- Texto dentro de la pill: 48 → 58 px + padding más amplio.
- Párrafo de reconocimiento: 32 → 36 px.
- Fecha + ciudad: 32 → 38 px.
- Cursiva firma (Andellia): 76 → 86 px.
- Nombre del firmante: 32 → 42 px.
- Cargo del firmante: 24 → 28 px.
- Footer copyright: 22 → 26 px.
- Posiciones verticales reajustadas para que todo quepa sin solaparse en cert de 1700 px de alto.
v1.13.0 — 2026-05-20
Cambiado
- Template del certificado aprobado por Pentair (mayo 2026). Reemplaza completamente el diseño anterior (bandas diagonales navy/blue/green + badge "TRAINING & EDUCATION") por el nuevo template diseñado por Rubén y aprobado por cliente en PLE-1462/PLE-1383.
- Fondo nuevo: foto realista de ola de agua arriba + triángulo decorativo cyan/navy en esquina inferior derecha. El PNG (3300×2550) vive en
public/cert/background.pngy se carga como data URL base64 porlib/certificado/background.ts(sharp lo redimensiona a la resolución del cert antes de embeber para mantener performance). - Layout: logo Pentair Pool centrado arriba sobre el agua, subtítulo "RECONOCEMOS LA PARTICIPACIÓN DE:", nombre gigante del asistente con auto-shrink según largo (150 / 135 / 115 / 90 px según número de caracteres), línea cyan corta, frase "Por completar satisfactoriamente el Seminario de Capacitación Pentair enfocado en:", pill con borde redondeado mostrando el tema/seminario, párrafo de reconocimiento "Reconociendo su compromiso con la excelencia técnica…", fecha + ciudad + país, 2 firmas con cursiva + línea cyan + nombre/cargo, footer "©AÑO Pentair. Todos los derechos reservados.", FOLIO discreto en esquina inferior izquierda.
- Empresa del asistente ya no se imprime en el certificado (sigue guardándose en BD para reportes/CSV).
- Soporte de 1 firma (centrada) o 2 firmas (lado a lado). 3 firmas legacy se truncan a las primeras 2 — el template aprobado contempla 2 fijas.
- Año del copyright se calcula automático con
new Date().getFullYear().
- Fondo nuevo: foto realista de ola de agua arriba + triángulo decorativo cyan/navy en esquina inferior derecha. El PNG (3300×2550) vive en
Pendiente confirmar con Andrea (PLE-1462)
- Si el texto de la pill debe venir del campo
nombredel evento (lo que hace ahora) o de un campo separado tipo "tema corto". - Si las firmas son globales fijas (Ramón Manrique + Andrés Bertossi del template) o siguen siendo editables por evento (lo que hace ahora).
- Si "Por completar..." y el párrafo largo son fijos o varían por seminario.
v1.12.0 — 2026-05-20
Cambiado
- Tipografía del sistema: Public Sans → Barlow. Toda la UI (admin, formulario público, login, changelog) y el certificado renderizado por satori usan ahora Barlow (OFL, github.com/jpt/barlow). Pesos en uso: 400 (body), 600 (labels/subtítulos), 700 (headers). Cambio en
app/layout.tsx(next/font/google),app/globals.css(variable CSS),lib/certificado/fonts.ts(satori),components/certificado/CertificadoLayout.tsx,DESIGN.md. Removidos los TTFs de Public Sans del repo.
Agregado
- Firmas en el certificado con Andellia Davilton (cursiva). Decisión operativa de Andrea (PLE-1382): no pedir scans de firma real a cada TSM; en su lugar, el nombre del firmante se renderiza en cursiva tipo manuscrita arriba de la línea horizontal, simulando una firma. Debajo sigue apareciendo el nombre en MAYÚSCULAS (Barlow Bold) + cargo. La fuente Andellia Davilton viene como WOFF; se convirtió a TTF con
fontToolspara que satori la entienda y se bundleó enpublic/fonts/AndelliaDavilton-Regular.ttf.
v1.11.5 — 2026-05-15
Arreglado
- Rayas verticales en el QR — fix definitivo de raíz: el problema NO era downscale del browser, era que el frame blanco se posicionaba con
gravity: 'center'y su ancho no era múltiplo entero del tamaño de módulo del QR. Resultado: los bordes del frame cortaban medio módulo dejando una rayita oscura visible a cada lado. Refactor completo del endpoint/api/qr/[slug]:- QR ahora se renderiza con
scale: N(pixeles por módulo enteros) en vez dewidth: 2048. Cada módulo es exactamente N pixeles, sin sub-pixel rendering. - Frame del logo se dimensiona en módulos enteros: cuántos módulos de ancho/alto debe ocupar, calculado para que la diferencia con el total sea par (centrado exacto sin half-module offset).
- Composite usa
top/leftcalculados (nogravity: 'center'), garantizando boundary exacta del módulo.
- QR ahora se renderiza con
v1.11.4 — 2026-05-15
Arreglado
- Rayas y logo chico en el QR (segunda iteración): el PNG fuente estaba limpio, las "rayas" eran artefacto del downscale del browser (PNG 1024px renderizado a ~470px en pantalla creaba bordes fuzzy por bilinear interpolation). Soluciones combinadas:
- QR ahora se renderiza a 2048 px (4× más pixeles para downscale limpio).
- Logo subido a 46% del ancho del QR, padding 25% — el logo "PENTAIR POOL" ahora es notoriamente prominente.
<img>del admin usaimage-rendering: crisp-edgespara que el browser no haga blur al escalar.
v1.11.3 — 2026-05-15
Arreglado
- Rayas verticales a los costados del logo del QR: cuando el ancho del frame blanco no se alinea con la grilla de módulos del QR, los bordes "cortan" medio módulo dejando rayitas oscuras visibles. Aumentado el padding interno del frame de 22% a 35% del logo — el frame ahora cubre módulos enteros con margen.
- Logo del QR muy pequeño: aumentado de 28% a 38% del ancho del QR. La marca "PENTAIR POOL" es ahora identificable a distancia.
v1.11.2 — 2026-05-15
Cambiado
- Logo del QR: el icono cuadrado de Pentair se reemplazó por el logo completo "PENTAIR POOL" (versión primary). Frame rectangular siguiendo el aspect ratio del logo, ocupa 28% del ancho del QR con padding interno de 22% — dentro del margen seguro de error correction H. La marca verbal hace el QR identificable a distancia sin afectar la escaneabilidad.
v1.11.1 — 2026-05-14
Arreglado
- Botón "Confirmar y obtener certificado" no respondía: el schema de v1.11.0 introdujo el campo
email_confirmpero el input nunca se renderizó enCheckinForm.tsx. RHF rechazaba el submit en silencio porqueerrors.email_confirmno estaba conectado a ningún<Field>. Restaurado el input "Confirma tu email" debajo del campo email, cononPaste/onDropbloqueados,autoComplete="off"y hint "Escríbelo, no lo pegues". Agregadoemail_confirm: ''adefaultValuespara mantener el input controlled desde el primer render.
v1.11.0 — 2026-05-12
Agregado
- Manejo de duplicados en check-in: si el mismo email ya hizo check-in en este evento y su cert fue generado, en lugar de regenerar muestra una pantalla amarilla "Ya estabas registrado. El certificado te llegará por email". Reduce bounces y costos de Resend por re-envíos accidentales.
- Doble validación de email en el check-in: campo "Confirma tu email" con validación Zod (refine — los dos deben coincidir). Pegar (paste) deshabilitado en el segundo campo para forzar que se escriba a mano. Previene typos que terminan en bounce.
- Base de datos de asistentes en
/admin/registros(nuevo nav link "Base de datos"):- Lista todos los check-ins históricos cruzados con info del evento.
- Filtros: búsqueda libre (nombre / email / empresa), evento, rango de fechas.
- Resultados ordenados por check-in descendente, top 500 por carga.
- Botón "Exportar CSV" que respeta los filtros activos. CSV con BOM UTF-8 para que Excel abra correctamente con tildes y eñes. Columnas: nombre, email, empresa, cargo, evento, ciudad, fecha del evento, asistio_at, satisfacción, recomendaría, comentario, cert_enviado_at.
- Edición de usuarios del panel (
/admin/usuarios/[id]/editar, solo admin):- Editar nombre, email y rol en un form.
- Cambiar contraseña en otro form independiente (bcrypt rehash).
- Guards: no puedes cambiar tu propio rol (te bloquearía); error si el email pertenece a otro usuario.
- Link "Editar" agregado en cada fila de la lista de usuarios.
v1.10.0 — 2026-05-12
Cambiado
- Ruta del check-in renombrada de
/checkin/[slug]a/evento/[slug]. El término "checkin" sonaba a software interno; "evento" comunica mejor lo que el asistente está abriendo. El QR generado, los links del admin, y todas las URLs visibles se actualizaron consistentemente. Los eventos existentes siguen funcionando — solo cambia la ruta. - Rediseño completo del hero del detalle de evento:
- QR ocupa una columna fija de 300px a la izquierda con border + descarga abajo.
- El panel derecho se divide en 3 secciones con
divide-y(más limpio que cards sueltas):- Estado del evento ahora es un segmented control de 4 pills clickeables con punto de color por estado. Reemplaza el select + botón Aplicar — el cambio es directo, un click. El estado actual se ve siempre con el color semántico (gris/verde/amarillo/azul Pentair).
- URL del evento en una fila propia con
CopiaryAbrirarriba a la derecha, código mono debajo en surface-sunken. - Opciones del evento con 2 ConfigRows (Email · Encuesta) — bordes neutros con un dot verde/gris que indica estado, label en caps + detalle en ink. Sin floods de verde. Link "Editar opciones →" al final.
- Ayuda de status ("¿Qué hace cada uno?") movida de un
<details>debajo del control a un popover discreto en el header de la sección.
Arreglado
- El status
enviadoahora se ve en azul Pentair (no surface-sunken neutro como antes). Diferencia visual clara contraborrador.
v1.9.0 — 2026-05-12
Agregado
- Página de configuración (
/admin/configuracion, solo rol admin) con 2 settings persistidos en DB:- Auto-cerrar eventos por inactividad: 1–72 horas (default 2). El cron de lifecycle lee este valor cada hora.
- Hora del envío diario de emails: 0–23 UTC (default 22 = 16:00 CDMX). Mostrado con su equivalente local CDMX para evitar errores de huso.
- Migración
0007con tablaapp_settings(key,value jsonb,updated_at,updated_by). Seed con defaults. lib/settings.tscongetAllSettings()ysetSetting()consumidos por crons y UI.- En el detalle del evento, el pill "Email" ahora muestra
Envío 16:00 CDMXcuando está activo (se actualiza al cambiar el setting). El operador entiende exactamente cuándo se procesan los certs.
Cambiado
- Solicitudes pendientes ahora en amarillo real (
yellow-50/300/600/700) en vez del ámbarwarning-*que se sentía más naranja. Diferencia visual clara entre "pendiente del admin" (amarillo) y warnings reales del sistema (naranja). - Cron de envío de emails ahora corre hourly (
0 * * * *) en vez de una vez al día. Internamente verificaemail_send_hour_utcy solo dispara el batch en la hora configurada; el resto de invocaciones retornan early conskipped: true. Esto permite cambiar la hora desde/admin/configuracionsin redeploy de Vercel.
v1.8.0 — 2026-05-12
Agregado
- Lifecycle automático del evento. Sabi ya no necesita cambiar manualmente el status — el sistema avanza solo:
- Abierto → Cerrado: cuando un evento abierto lleva más de
EVENT_AUTO_CLOSE_HOURShoras (default 2) sin nuevos check-ins. Solo aplica si hubo al menos 1 check-in (eventos abiertos sin asistencia no se cierran solos — pueden estar esperando que arranque el seminario). - Cerrado → Enviado: cuando ya no hay emails pendientes en la cola. Si
usar_email = falseel cambio es inmediato; si está prendido, espera a que todos los registros conasistio=truetenganenviado_atpoblado.
- Abierto → Cerrado: cuando un evento abierto lleva más de
- Nuevo cron
/api/cron/event-lifecyclecorriendo cada hora (0 * * * *). Auth conBearer CRON_SECRETigual que el cron de email. - El cron retorna JSON con
cerrados[],enviados[]yskipped[]con razones, útil para diagnosticar transiciones que no pasaron como se esperaba.
Notas
EVENT_AUTO_CLOSE_HOURSes env var opcional en Vercel. Sin ella, default = 2.- El status manual sigue funcionando — Sabi puede cerrar antes o saltar a Enviado desde el admin en cualquier momento. El cron no revierte cambios manuales.
v1.7.0 — 2026-05-12
Agregado
- Página 404 con branding Pentair. Antes Next servía la 404 default. Ahora:
- Logo Pentair Pool, "404" grande con tinte azul translúcido + label "página no encontrada" superpuesto.
- Mensaje cálido en español explicando que el link no existe o el evento fue cerrado.
- Dos CTAs: "Página principal" (primary) y "Ir al panel" (secondary).
- Card de ayuda para asistentes que llegaron equivocados: explica que el check-in solo funciona durante el evento vía el QR del instructor.
- Footer global con versión.
- Documentación retroactiva en Linear: todas las features de v1.0–v1.6 ahora tienen issue tracking con descripción markdown y status correcto (Done / Cancelled), agrupadas en los milestones del proyecto.
v1.6.0 — 2026-05-12
Agregado
- Sección de ayuda de status en el detalle del evento. Antes solo había un select con
Borrador / Abierto / Cerrado / Enviadosin explicación. Ahora hay una nota con los 4 estados, qué significa cada uno y cuándo usarlos.
Cambiado
- Color de las solicitudes pendientes de azul a ámbar (
amber-600+amber-100). Comunica mejor "acción pendiente del admin" sin competir con el azul de acción primaria. - Versionado bumped a 1.x. Las primeras 6 releases pasaron de
v0.1–v0.6av1.0–v1.5. Esta esv1.6.0y de aquí en adelante se mantiene en mayor 1 hasta el próximo pivot arquitectónico.
v1.5.0 — 2026-05-12
Agregado
- Eliminar evento (solo rol
admin) con confirmación nativa. FK CASCADE borra los registros del evento; mejor esfuerzo de limpieza de PDFs/JPGs en Supabase Storage. - Toggles por evento en alta y edición:
Enviar certificado por email— si está apagado, el cron diario no procesa este evento.Mostrar encuesta de satisfacción— si está apagado, el check-in público omite las 3 preguntas.
- Ambos toggles vienen marcados por default.
- Dominio Resend
cert.pentairlatam.comverificado. Los certificados salen desdePentair Pool LATAM <noreply@cert.pentairlatam.com>con DKIM, SPF, MX y DMARC propagados. - Footer global con versión del sistema (este changelog).
Cambiado
- Vista de detalle de evento rediseñada:
- Hero con preview del QR inline (imagen 240×240 visible, no escondido en card) + botón Descargar PNG, URL del check-in con botón Copiar y Abrir, y selector de status en el mismo bloque.
- Indicadores visuales del estado de los toggles email/encuesta.
- Stats movidos arriba (asistentes, enviados, satisfacción, recomienda).
- Firmantes en aside compacto con acceso rápido a la preview del certificado.
- Tabla de asistentes en el bloque principal.
/api/qr/[slug]acepta?inline=1para servir el PNG inline (necesario para mostrar la preview en la vista de admin). Sin query, sigue forzando descarga como antes.
Arreglado
- Toast de éxito/error en
/admindespués de eliminar o cualquier redirect con?ok=o?error=.
v1.4.0 — 2026-05-12
Agregado
- Logo oficial Pentair Pool integrado en todas las superficies:
- Cert PDF/JPG en blanco sobre la diagonal navy.
- Header admin, login, landing, y check-in con el logotipo a color.
- Favicon del navegador con el mark/chevron (auto-detectado por Next).
- QR con logo Pentair en el centro generado con
sharpcomponiendo el favicon SVG sobre el QR. Error correction H tolera el 22% del área cubierta. - Iconos sociales reales en la pantalla de éxito del check-in (
components/BrandIcons.tsxcon SVG inline de Facebook, WhatsApp y YouTube). Lucide v1 retiró brand icons por trademark. - Links de comunidades Pentair Pool LATAM (Facebook, WhatsApp, YouTube) en el check-in success.
- Flujo de solicitud de acceso (
/solicitar-acceso) para usuarios externos:- Nombre, email, password (mínimo 10 chars) y motivo opcional.
- Migración
pending_access_requestscon hash bcrypt del password. - El admin ve solicitudes pendientes en
/admin/usuariosy aprueba (mueve aadmin_userscon rol elegido) o rechaza (borra). Solo rol admin puede aprobar.
- Login mejorado:
- Placeholder genérico
nombre@correo.com. - Iconos
MailyLockdentro de los inputs. - Toggle
Eye/EyeOffpara mostrar/ocultar el password. - Botón "Entrar" con
ArrowRight. - Card secundaria con link a
/solicitar-acceso.
- Placeholder genérico
Cambiado
- Colores corregidos al brand oficial Pentair Pool LATAM (de los SVG entregados por el cliente):
pentair-blueahora es#004E9A(antes aproximación#0072CE).pentair-cyanahora es#4BC6DF(antes#06B6D4).
- Escala de color siempre visible en la encuesta del check-in: 1 rojo · 2 naranja · 3 ámbar · 4 azul Pentair · 5 verde Pentair. Al seleccionar, color full + texto blanco + shadow.
- Botones Sí/No de recomendación con tinte verde/ámbar siempre, color full al seleccionar.
Arreglado
- Cert no se generaba (
Unsupported OpenType signature wOF2): Google Fonts dejó de devolver TTF incluso con UA IE9 para Public Sans. Solución: bundlear los 4 TTFs de Public Sans (OFL, USWDS) enpublic/fonts/y leerlos confs.readFile.next.config.tsahora declaraoutputFileTracingIncludespara que Vercel suba los TTFs con las funciones que los necesitan. - Footer del cert no se renderizaba con 1 o 3 firmas: el fragment de React no funciona dentro de posición absoluta en satori. Refactor a dos bloques condicionales independientes con
display: flexexplícito. - Texto con separador
·en un mismo<div>rompía satori (3 nodos hijos sindisplay: flex). Cambio a template string como hijo único. - Adaptación de tipografía: nombres y empresas con más de 32 caracteres reducen tamaño automático para evitar overflow.
- PDF se descarga en vez de abrir en pestaña:
createSignedUrlahora pasa la opcióndownload: 'filename'que añadeContent-Disposition: attachmental response de Supabase Storage. Antes el atributo HTMLdownloadse ignoraba porque las URLs de Storage son cross-origin.
Iteraciones internas
- v1.4 incluye una corrección a una mala interpretación: por error se removió la encuesta de check-in pensando que el usuario lo pedía. Se restauró rápidamente con la misma escala de color y stats.
v1.3.0 — 2026-05-12
Agregado
- Sistema de diseño formal (
DESIGN.mden raíz del repo) inspirado enpentair.novem.com.mx, las guías deimpeccable.styley el formato Google Stitch / VoltAgent. Tokens completos: colores, tipografía, espaciado, componentes, do's/don'ts. - Tipografía Public Sans + JetBrains Mono reemplaza Inter/Geist (fingerprints de UI generada con AI según
impeccable.style). - Set único de iconos outline vía
lucide-reactreemplazando los SVG inline ad-hoc. - Preview del certificado desde admin: endpoint
/api/admin/preview-cert/[id]que renderiza con datos de ejemplo y devuelveimage/jpeg. Botón "Ver preview" en el detalle del evento. - Estadísticas generales en
/admin: total de eventos, asistentes, certs enviados, promedio de satisfacción, % recomendaría. Card destacada del próximo evento. - Gestión de usuarios del panel (
/admin/usuarios):- Migración
admin_userscon email único, hash bcrypt, nombre, rol (adminoeditor),last_login_at. - Seed inicial con
alejandro@plexiz.comy el password original. - Login ahora pide email + password (bcrypt compare en server action).
- CRUD básico: crear, listar, eliminar (con guardas: no puedes eliminar tu propio usuario ni dejar la tabla vacía).
- Solo rol
adminpuede acceder a/admin/usuarios. - Header admin muestra nombre + email del usuario activo.
- Migración
Cambiado
- Paleta de UI anclada a Pentair: navy + slate neutros, con
pentair-bluecomo único color de acción. Sin sombras pesadas, sin gradientes morados, sin cards anidadas. - Tailwind 4
@themecon tokens semánticos:pentair-*,surface,surface-muted,surface-sunken,line,line-strong,ink,ink-soft,ink-muted,success/warning/danger. - Cookie de sesión renombrada a
pentair_admin_usercon UUID del usuario. - Empresa marcada como opcional en el check-in (antes requerida).
Arreglado
- Bug crítico de generación de cert con WOFF2 y satori (parche inicial, refinado en v1.4).
- Slug se autogenera del nombre + ciudad — ya no se pide al operador.
- Firmantes con default 2 + botón para agregar hasta 3.
cargoremovido del check-in (no aparece en el cert).
v1.2.0 — 2026-05-08
Agregado
- Pivot al flujo QR check-in con descarga instantánea:
- El asistente escanea el QR al final del seminario, entra a
/checkin/[slug]con datos del evento prellenados. - Llena nombre, email, empresa, y 3 preguntas de encuesta de satisfacción.
- Recibe PDF + JPG descargables al instante desde la pantalla de éxito.
- El asistente escanea el QR al final del seminario, entra a
- Render del cert reescrito como pipeline único: HTML/CSS (satori) → SVG → resvg → PNG → sharp → JPG, y pdf-lib envuelve el PNG en un single-page PDF. Una sola fuente de verdad para los dos formatos de salida.
- Storage privado en Supabase con signed URLs de 30 días para cada cert.
- API
/api/qr/[slug]que genera el QR PNG conqrcodelib y colores Pentair. - Cron diario (
/api/cron/send-emails) con authBearer CRON_SECRET, throttle 600ms entre envíos, límite 95/día para mantenerse en el free tier de Resend. Adjunta PDF + JPG al email. - Encuesta integrada en el form de check-in (satisfacción 1-5, recomendaría sí/no, comentario opcional). Stats agregadas en admin.
- Migración
0003:encuesta_respuestas+cert_pdf_url+cert_jpg_urlenregistros. vercel.jsoncon schedule del cron a las 22:00 UTC daily (16:00 CDMX).
Removido
- Form de pre-registro
/registro/[evento]— Typeform reemplaza el pre-registro fuera del sistema. - Sistema de templates múltiples — se decide usar un solo "diseño anual" como pidió Bobe.
- Toggle manual de asistencia en admin — el check-in del propio asistente la marca automático.
v1.1.0 — 2026-05-08
Agregado
- Sistema de templates de certificado seleccionable por evento: 2 templates iniciales (
herramientas-exito-clarocon fondo claro + flechas decorativas, yherramientas-exito-diagonalcon diagonal navy + verde). - Registry de templates en
components/certificado/templates/registry.tsxcon label, descripción y componente por key. - Form de nuevo evento con dropdown de template + firmantes (1 requerido, hasta 2) y nombres+cargos editables.
- Form de editar evento permite cambiar template y firmas después de creado sin perder registros.
enviarCertificadoActionleetemplate_keyyfirmasdel evento y los pasa al renderer./cert-preview?template=...para alternar entre plantillas.
Cambiado
- Migración
0002:eventos.template_key+eventos.firmas(jsonb array de{nombre, cargo}).
v1.0.0 — 2026-05-07
Agregado
- Scaffold inicial del sistema:
- Next.js 16 (App Router) + TypeScript + Tailwind 4.
- Supabase (Postgres + Storage privado bucket
certificados). @react-pdf/rendererpara generación de certificados (sustituido en v1.2 por satori + resvg + pdf-lib).- Resend +
react-emailpara envío transaccional. - Zod + React Hook Form para validación.
- Migración inicial: tablas
eventos(slug único, nombre, ciudad, fecha, status, template_key, firmas, timestamps) yregistros(FK al evento, nombre, email, empresa, cargo, asistio, asistio_at, cert_path, enviado_at, email_status, timestamps). - Trigger
set_updated_atpara mantenerupdated_atfresco. - RLS habilitado en ambas tablas; policy "public lee eventos abiertos" para el form de registro.
- Build vertical end-to-end del demo: form público de registro, componente cert con branding placeholder Pentair Pool LATAM, template email con
react-email, envío vía Resend, admin minimal con auth por env password, CRUD eventos, check-in y dispatch por registro. - Preview del certificado en
/cert-preview. - Deploy inicial en Vercel (team
plexiz) con dominio customcert.pentairlatam.comresolvido a través de Cloudflare DNS.
Estados de un evento — referencia
Un evento pasa por hasta 4 estados durante su ciclo de vida. El admin elige el status manualmente; el sistema no lo cambia solo.
| Status | Qué hace | Cuándo usarlo |
|---|---|---|
| Borrador | La URL pública /checkin/[slug] devuelve 404. No acepta registros. | Mientras estás preparando el evento (cuadrar firmantes, ajustar nombre, etc.) y aún no quieres que nadie pueda hacer check-in. |
| Abierto | Check-in público activo. Los asistentes pueden escanear el QR y registrarse. | Cuando el evento está listo para recibir asistencia. Es el estado activo más común. |
| Cerrado | Ya no acepta nuevos check-ins. Los asistentes que ya se registraron siguen visibles. | Después del seminario, para evitar registros tardíos. El cron de email puede seguir mandando si usar_email está prendido. |
| Enviado | Estado cosmético final. No cambia comportamiento, solo etiqueta el evento como "concluido". | Después de confirmar que todos los certs fueron enviados y el evento está totalmente archivado. |
Lifecycle típico: Borrador → Abierto → Cerrado → Enviado. Nada te impide saltar pasos (puedes ir directo a Abierto si no necesitas un borrador, o saltarte Cerrado).
Notas operativas
Stack de producción
| Capa | Servicio |
|---|---|
| Hosting | Vercel (team plexiz) |
| Base de datos | Supabase Postgres (org Plexiz, region us-east-1) |
| Storage | Supabase Storage (bucket privado certificados, 30 días signed URLs) |
Resend (dominio cert.pentairlatam.com verificado, free tier 100/día) | |
| DNS | Cloudflare |
| Repositorio | github.com/alejandroplexiz/certificacion-pentair (privado) |
Convención de versiones
- MAJOR (
x.0.0) — Reservado para reescrituras o pivots arquitectónicos. Por ahora estamos en1.x(v1.0 fue el primer deploy productivo). - MINOR (
1.x.0) — Features nuevas visibles para Sabi o asistentes. - PATCH (
1.0.x) — Fixes y polish sin cambios de comportamiento.
Próximas iteraciones candidatas
- Auth con magic link Supabase Auth (reemplazo del bcrypt actual).
- Webhook de Resend para tracking de bounces/complaints + reintentos.
- Soporte de varios diseños de certificado (si Pentair cambia campaña).
- Dashboard de analítica por TSM o por región.
- Exportar lista de asistentes a CSV.