home
  • Spanish (ES)
  • English (UK)
  • Portuguese (PT)
  • star Destacados
  • Hermes Agent
  • draw Proyectos UX
  • person Sobre mí
  • mail Contacto
arrow_back Volver al proyecto Inicio

Documentación del proyecto

Documentación · Presencia

11 archivos markdown (producto, arquitectura, API, auth, datos, deploy…).

En esta página Visión general 0% expand_more
Visión general Enlaces Stack Producto y features Problema Solución Características Dashboard Log diario Historial Cursos Ajustes Offline-first i18n Arquitectura Diagrama lógico Capas Principios Estructura del proyecto Scripts Superficie API Neon En producción (same-origin) En desarrollo Operaciones de producto Por qué no hay /api Nest Autenticación email + Neon Flujo Seguridad Por qué email/password y no PIN Modelo de datos Offline (localStorage) Tipos (attendance.model.ts) Course DayRecord MonthStats (derivado) Cálculo de asistencia Nube Deploy Netlify Idea Config (netlify.toml) Variables Smoke Local Decisiones de producto y tech Neon Auth/Data en lugar de Nest propio Proxy Netlify same-origin Offline primero, sync opt-in localStorage en vez de Ionic Storage Signal Forms en Angular 21, Zone por Ionic Netlify, no Vercel Retos y resultados Cookie de sesión en PWA iOS Convergencia local ↔ nube Cálculo de % vs sensación del usuario Periodos mes vs módulo Skill Angular Qué se aplica Techo Ionic 8 (igual que Task Cloud)

01-overview.md

Visión general · Presencia

Presencia (repo assistance-tracker) es una Progressive Web App mobile-first para llevar la asistencia de uno o varios cursos: presencia, falta, impuntualidad y clase cancelada. Funciona offline por defecto y sincroniza a la nube si el usuario abre sesión.

A diferencia de Task Cloud, es una sola app Angular/Ionic (no monorepo Nest). La nube es Neon Auth + Data API, expuesta same-origin vía proxy de Netlify para que la cookie de sesión sea first-party (iOS Safari / PWA instalada).

Enlaces

  • Web: https://asistenciacurso.netlify.app
  • GitHub: Criscode2022/assistance-tracker
  • Producto: control mensual de asistencia a cursos (package.json name presencia)

Stack

CapaTecnología
WebAngular 21.2, Ionic 8, Capacitor 8, Tailwind 4, Signal Forms
OfflinelocalStorage (courses_v1, attendance_v3)
NubeNeon Auth + Neon Data API (@neondatabase/neon-js)
i18nngx-translate ES / EN
DeployNetlify (www/ + SPA rewrite + proxy /__neon-*)

02-product.md

Producto y features

Problema

Llevar faltas y retrasos en Excel, WhatsApp o la memoria falla cuando hay varios cursos, un mínimo de asistencia y tardanzas que restan minutos. Las apps de campus suelen exigir cuenta institucional. Hacía falta un registro preciso, usable en el móvil, sin red, y con sync opcional a una cuenta personal.

Solución

PWA instalable (Presencia) con dashboard de salud del curso, log diario, historial por periodo y CRUD de cursos. Offline-first en localStorage. Si el usuario activa el modo online, Neon Auth (email/password + verificación) y Data API sincronizan cursos y registros.

Características

Dashboard

Anillo de % de asistencia (verde / ámbar / rojo / gris), tarjetas de faltas y tardanzas frente al máximo, horas asistidas y minutos perdidos.

Log diario

Cada día laborable del periodo es una card. Tap → action sheet (presente, impuntual con horas, falta, cancelada, sin registrar). Swipe para toggle rápido presente/falta.

Historial

Resumen mes a mes o por módulos: %, ratio de días, horas.

Cursos

Alta/edición de horario, horas/día, máximos, % mínimo, modo mes o módulos. Import/export JSON.

Ajustes

Tema, idioma, notificaciones (recordatorio diario y alerta de %), modo online/offline, borrar todo.

Offline-first

Sin red, el registro diario no se bloquea. La nube es continuidad entre dispositivos, no el camino crítico de cada tap.

i18n

Inglés y español, fechas según locale.

03-architecture.md

Arquitectura

Diagrama lógico

Browser / PWA Ionic (Angular 21)
    │  offline: localStorage
    │  online + sesión: Neon JS client
    ▼
Same-origin  /__neon-auth/*  y  /__neon-data/*
    │  (Netlify reverse proxy)
    ▼
Neon Auth  +  Neon Data API  ──►  Postgres (proyecto wild-breeze-65639945)

Capas

  1. Cliente — Tabs Ionic: Dashboard, Log, Historial, Cursos. Settings y Auth fuera de la tab bar. Servicios: AttendanceService, NeonService, CloudSyncService, AppModeService.
  2. Offline — courses_v1 + attendance_v3 en localStorage, con migraciones desde attendance_v2.
  3. Nube — @neondatabase/neon-js: Auth (email/password) y Data API. No hay Nest propio: el BaaS de Neon es la API.
  4. Deploy — Netlify sirve www/ y reescribe /__neon-auth/* y /__neon-data/* al host Neon para cookie first-party.

Principios

  • Offline primero; sync opt-in.
  • Sesión first-party (ITP / PWA iOS).
  • Sin analytics ni tracking.
  • Secretos de Neon no van al bundle más allá de las URLs públicas de Auth/Data (en prod, paths same-origin).

04-monorepo.md

Estructura (app única)

Presencia no es un monorepo Turborepo: un solo paquete Angular/Ionic, equivalente a apps/web de Task Cloud.

PathRol
src/app/dashboardTab: anillo y métricas
src/app/logTab: días del periodo
src/app/historyTab: resumen por mes/módulo
src/app/coursesTab: CRUD e import/export
src/app/authSign-in / sign-up
src/app/configAjustes (fuera de tabs)
src/app/servicesDatos, sync, tema, i18n, notificaciones
src/app/models/attendance.model.tsCourse, DayRecord, MonthStats
src/environmentsURLs Neon (prod: /__neon-auth, /__neon-data)
netlify.tomlBuild, proxy Neon, SPA fallback
capacitor.config.tscom.presencia.app

Scripts

ScriptDescripción
npm start / ionic serveDev en :8100
npm run buildProducción → www/
npm run test:ciKarma headless
npm run e2e:ciPlaywright

Package manager: npm. Node ≥ 20. Angular CLI 21.

05-api.md

Superficie API (Neon Auth + Data)

No hay Nest propio. El cliente habla con Neon a través de @neondatabase/neon-js.

En producción (same-origin)

PrefijoDestino (Netlify rewrite 200)
/__neon-auth/*Neon Auth (*.neonauth.*.aws.neon.tech/neondb/auth)
/__neon-data/*Neon Data API REST v1 (*.apirest.*.aws.neon.tech/neondb/rest/v1)

Así la cookie de sesión es first-party en asistenciacurso.netlify.app y sobrevive en PWA iOS.

En desarrollo

environment.ts apunta a las URLs absolutas de Neon (sin proxy).

Operaciones de producto

El sync (CloudSyncService) sube y baja:

  • Cursos del usuario autenticado
  • Registros de asistencia (DayRecord por fecha)

Tras registro, los datos locales se suben. Tras login, la nube reemplaza lo local del dispositivo (documentado en el README).

Por qué no hay /api Nest

El dominio es un único cliente PWA. Auth + persistencia cubiertas por Neon Auth/Data API evitan un segundo servicio que desplegar. Task Cloud necesita Nest porque el contrato (PIN, bulk tasks, ownership) es custom.

06-auth.md

Modelo de autenticación

Flujo

  1. El usuario crea cuenta (nombre, email, contraseña) o inicia sesión.
  2. Neon Auth (client.auth.signUp.email / signIn.email).
  3. Verificación de email antes de poder entrar (requisito Neon Auth).
  4. getSession() calienta la caché; en PWA iOS la cookie puede no leerse al instante — no se trata session === null inmediato como fallo de login.
  5. Logout: signOut(). Los datos locales se conservan en el dispositivo.

Seguridad

  • Contraseña y tokens los gestiona Neon Auth; el bundle no guarda secretos de servidor.
  • Proxy same-origin para no perder la sesión por ITP.
  • Cloud sync es opt-in (modo online en Ajustes).

Por qué email/password y no PIN

El caso de uso es un estudiante/profesional con cuenta recuperable entre dispositivos. Neon Auth ya ofrece email + verificación. Un PIN local no cubría “el mismo curso en el portátil y en el móvil” sin un backend custom como el de Task Cloud.

07-data-model.md

Modelo de datos

Offline (localStorage)

ClaveContenido
courses_v1Course[]
attendance_v3Mapa curso → fecha → DayRecord
selected_course_idCurso activo
(legado) attendance_v2Migrado al arrancar

Tipos (attendance.model.ts)

Course

id, name, startDate / endDate, startTime, hoursPerDay, maxAbsences, maxTardiness, minAttendancePercent, periodMode (month | module), modules?.

DayRecord

status: present | absent | late | unlogged | cancelled.
Si late: entryTime / exitTime (HH:mm).

MonthStats (derivado)

Días laborables, presentes/faltas/tardanzas, % = horas asistidas / horas esperadas a fecha, minutos perdidos, overallStatus: ok | warning | failed.

Cálculo de asistencia

  • Días laborables: lun–vie en el rango del curso (o del módulo).
  • Impuntualidad resta minutos (entrada/salida vs horario).
  • Clase cancelada no cuenta como falta ni como hora esperada.

Nube

Neon Data API persiste cursos y registros ligados al user.id de Neon Auth. El cliente no expone SQL; el contrato es el Data API REST.

08-deploy-vercel.md

Deploy en Netlify

Idea

  • Build Angular → www/
  • Catch-all SPA: /* → /index.html 200
  • Proxy Neon antes del catch-all para Auth y Data API

Config (netlify.toml)

[build]
  publish = "www"
  command = "npm run build -- --configuration production"

[[redirects]]
  from = "/__neon-auth/*"
  to = "https://<endpoint-neon-auth>/neondb/auth/:splat"
  status = 200
  force = true

[[redirects]]
  from = "/__neon-data/*"
  to = "https://<endpoint-neon-data>/neondb/rest/v1/:splat"
  status = 200
  force = true

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Variables

En producción las URLs de Auth/Data van same-origin (environment.prod.ts). El proyecto Neon (wild-breeze-65639945) se configura en el dashboard de Neon; no hay DATABASE_URL en el cliente.

Smoke

  1. Abrir https://asistenciacurso.netlify.app
  2. Crear un curso y marcar un día sin cuenta (offline).
  3. Activar modo online → registro/login → comprobar que el curso reaparece en otro navegador.

Local

npm install
ionic serve   # :8100

Sin cuenta Neon, el modo offline cubre todo el producto excepto sync.

09-decisions.md

Decisiones

Neon Auth/Data en lugar de Nest propio

Un segundo servicio (como Task Cloud) no aportaba contrato custom: email, sesión y filas por usuario caben en Neon Auth + Data API. Menos ops, un solo deploy estático.

Proxy Netlify same-origin

Sin proxy, la cookie de Auth es third-party y iOS/PWA la tiran. El rewrite /__neon-* es una decisión de producto (sesión persistente), no cosmética.

Offline primero, sync opt-in

El registro del día no puede depender de cobertura en el aula. localStorage es la fuente en el dispositivo; la nube es continuidad.

localStorage en vez de Ionic Storage

El modelo cabe en JSON versionado (v1/v3). Ionic Storage habría añadido un adapter sin ganar queries. Se documentan migraciones explícitas.

Signal Forms en Angular 21, Zone por Ionic

Formularios de curso y auth usan @angular/forms/signals. Ionic 8 sigue pidiendo Zone; no se fuerza zoneless.

Netlify, no Vercel

SPA + redirects + proxy encajan en netlify.toml sin función serverless. Task Cloud necesita Vercel por Nest; aquí no.

10-challenges-results.md

Retos y aprendizajes

Cookie de sesión en PWA iOS

Safari ITP bloquea cookies third-party. El login “funcionaba” en desktop y se perdía al “Añadir a pantalla de inicio”. Solución: reverse proxy same-origin + no fallar si getSession() tarda un tick.

Convergencia local ↔ nube

Tras login, la nube sustituye lo local (documentado). Tras registro, se sube lo que ya había en el móvil. Evita merges silenciosos de dos calendarios distintos.

Cálculo de % vs sensación del usuario

El % usa horas (tardanza resta minutos), no solo días. Hay que explicarlo en el dashboard (chip de minutos perdidos) para que “estuve pero llegué tarde” no parezca un bug.

Periodos mes vs módulo

Cursos que no coinciden con el mes natural (módulos) obligaron a periodMode y a un selector de periodo compartido entre tabs.

Resultados

  • PWA en asistenciacurso.netlify.app, instalable, usable 100% offline.
  • Dashboard, log, historial y cursos con i18n ES/EN y tema claro/oscuro.
  • Sync opt-in con Neon Auth + Data API y cookie first-party.
  • e2e Playwright + tests de servicios; Capacitor preparado (com.presencia.app).
  • Código abierto como referencia Angular/Ionic + Neon + Netlify (complemento de Task Cloud: Nest + Vercel).

11-angular-skill.md

Angular en Presencia

App Angular 21.2 + Ionic 8 + TypeScript 5.9 + Zone. Formularios con Signal Forms (@angular/forms/signals), como indica el README. No hay skill Angular publicada en este repo (a diferencia de Task Cloud, que versiona .claude/skills/angular).

Qué se aplica

PrácticaDónde
Signal FormsAlta/edición de curso y validación (mín. %, fechas, horas)
Standalone / módulos IonicTabs y páginas; shell Ionic
ngx-translateES / EN
Service workerPWA + notificaciones
TestsKarma unit + Playwright e2e

Techo Ionic 8 (igual que Task Cloud)

API modernaPor qué no se fuerzaQué hay
ZonelessIonic 8 pide zone.jszone.js ~0.15
[formField] en ion-*Los controles Ionic no implementan FormValueControlSignal Forms en validación; ion-* escribe el modelo
View Transitions del Router AngularNavegación por ion-tabs / ion-router-outletTransiciones Ionic
SSRPWA + localStorage + CapacitorSPA en Netlify

La revisión de Task Cloud (Angular 22 + httpResource) no se copia aquí: Presencia no tiene Nest que leer con httpResource; el cliente Neon JS cubre Auth/Data.

En esta página

Visión general Enlaces Stack Producto y features Problema Solución Características Dashboard Log diario Historial Cursos Ajustes Offline-first i18n Arquitectura Diagrama lógico Capas Principios Estructura del proyecto Scripts Superficie API Neon En producción (same-origin) En desarrollo Operaciones de producto Por qué no hay /api Nest Autenticación email + Neon Flujo Seguridad Por qué email/password y no PIN Modelo de datos Offline (localStorage) Tipos (attendance.model.ts) Course DayRecord MonthStats (derivado) Cálculo de asistencia Nube Deploy Netlify Idea Config (netlify.toml) Variables Smoke Local Decisiones de producto y tech Neon Auth/Data en lugar de Nest propio Proxy Netlify same-origin Offline primero, sync opt-in localStorage en vez de Ionic Storage Signal Forms en Angular 21, Zone por Ionic Netlify, no Vercel Retos y resultados Cookie de sesión en PWA iOS Convergencia local ↔ nube Cálculo de % vs sensación del usuario Periodos mes vs módulo Skill Angular Qué se aplica Techo Ionic 8 (igual que Task Cloud)

0% leído

folder_zip Descargar todos (ZIP)
Hermes Agent