home
  • Spanish (ES)
  • English (UK)
  • Portuguese (PT)
  • star Featured
  • Hermes Agent
  • draw UX Projects
  • person About me
  • mail Contact
arrow_back Back to project Home

Project documentation

Documentation · Task Cloud

11 markdown files (product, architecture, API, auth, data, deploy…).

On this page Visión general 0% expand_more
Visión general Enlaces Stack Producto y features Problema Solución Características CRUD de tareas Prioridad y tags Filtros por estado Offline-first Sync unitario y bulk Auth PIN + sesión PWA instalable i18n Arquitectura Diagrama lógico Capas Principios Monorepo Turborepo Scripts raíz Workspaces Superficie API Nest Rutas públicas Auth (protegidas) Tareas (protegidas) Ownership Autenticación PIN + JWT Flujo Seguridad Por qué PIN y no email/password Modelo de datos Neon Tablas users tasks sessions Índices Migraciones Deploy Vercel (Turborepo) Idea Config (vercel.json) Variables de entorno Smoke Local Decisiones de producto y tech Neon + Nest en lugar de Supabase client Turborepo monorepo + un deploy Offline primero, sync opcional PIN en vez de email/password Angular 22 con Zone (no zoneless) por Ionic Retos y resultados Serverless Nest en Vercel Convergencia local ↔ nube Env dual (web + api) Skill Angular Revisión 2026-08-13 (Angular 22) Qué Angular 22 no se puede (o no se debe) aplicar por Ionic 8 House style (extracto) Referencia en el repo de la app

01-overview.md

Visión general · Task Cloud

Task Cloud es una Progressive Web App de gestión de tareas personales con soporte offline real y sincronización en la nube.

El repositorio es un monorepo npm workspaces + Turborepo:

  • apps/web — Angular 22 + Ionic 8 (PWA)
  • apps/api — NestJS 11 sobre Neon Postgres

En producción se despliega como un único proyecto Vercel: el front se sirve como estáticos y Nest se envuelve en una función serverless (api/index.js) detrás de /api/* (same-origin, sin CORS).

Enlaces

  • Web: https://task-manager-cristiancode.vercel.app
  • GitHub: Criscode2022/task-manager-cloud
  • Health: GET /api/health → { "ok": true, "service": "task-cloud-nest-api" }

Stack

CapaTecnología
MonorepoTurborepo + npm workspaces
WebAngular 22, Ionic 8, Tailwind, service worker
APINestJS 11, jose, bcryptjs
DatosNeon Postgres (@neondatabase/serverless)
DeployVercel (static + serverless function)

02-product.md

Producto y features

Problema

Las apps de tareas suelen exigir registro completo, fallan sin red o acoplan el cliente a un BaaS monolítico. El objetivo era una PWA instalable, usable 100% offline, con sync opcional controlada por una API propia sobre Postgres serverless (Nest + Neon + Vercel).

Solución

Cliente offline-first con Ionic Storage. Con sesión activa, sube y baja tareas vía Nest. Auth ligera por PIN de 8 dígitos (hash + pepper) y sesiones JWT. Deploy unificado en Vercel.

Características

CRUD de tareas

Crear, editar, completar y eliminar con feedback inmediato en la UI Ionic.

Prioridad y tags

Campos priority (low / medium / high) y tags[] en Postgres, con índices GIN.

Filtros por estado

Vistas de pendientes, completadas o todas, orientadas al uso diario en móvil.

Offline-first

Ionic Storage mantiene la lista usable sin red; la nube no es SPOF de la UI.

Sync unitario y bulk

API REST: POST /tasks, POST /tasks/bulk, PUT, DELETE protegidos por JWT.

Auth PIN + sesión

Registro/login con PIN, JWT (sub + sid), tabla sessions y rate-limit en auth.

PWA instalable

Service worker Angular y layout app-like en móvil y escritorio.

i18n

ngx-translate con idiomas EN/ES en el cliente.

03-architecture.md

Arquitectura

Diagrama lógico

Browser (PWA Ionic/Angular)
    │  offline: Ionic Storage
    │  online + sesión: HTTP /api/*
    ▼
NestJS (apps/api)  ──@neondatabase/serverless──►  Neon Postgres
    ▲
api/index.js (Vercel serverless, warm cache)

mcp-server/ (opcional, Python FastMCP) ──► Neon

Capas

  1. Cliente — Angular 22 + Ionic 8 + Tailwind. Servicios de dominio: TaskService, TaskNeonService, NeonApiService. Techo v22×Ionic: 11-angular-skill.md.
  2. API — NestJS 11: módulos auth, users, tasks, health, rate-limit y exception filter.
  3. Datos — Neon Postgres: users, tasks, sessions.
  4. Deploy — Un proyecto Vercel: turbo build web+api; output apps/web/www; rewrites /api/* → función.
  5. Local — API :3001, web :4200 con proxy /api; npm run start:full.

Principios

  • Offline primero; sync opcional.
  • Secretos solo en servidor (DATABASE_URL, JWT_SECRET, PIN_PEPPER).
  • Same-origin en producción (API_BASE_URL=/api).

04-monorepo.md

Layout del monorepo

PathRol
apps/webPWA Ionic/Angular (output www/)
apps/apiNestJS (dist/) consumido por la función Vercel
api/index.jsEntry serverless: boot Nest + cache warm
packages/Reservado para shared packages
mcp-server/MCP Python opcional (FastMCP + uv)
vercel.jsonBuild, functions, rewrites SPA + /api
turbo.jsonTasks, env y remote cache

Scripts raíz

ScriptDescripción
npm run apiNest en watch
npm startAngular + proxy /api
npm run start:fullAPI + web vía turbo
npm run buildBuild de todos los packages
npm run build:web / build:apiBuild selectivo
npm run test:ciTests web headless
npm run test:apiSmoke HTTP de la API

Workspaces

"workspaces": ["apps/*", "packages/*"]

Package manager: npm 10. Node ≥ 20.

05-api.md

Superficie API (Nest)

Prefijo global: /api. Rutas de tareas y sesión protegidas con Authorization: Bearer <jwt>.

Rutas públicas

MétodoPathNotas
GET/api/healthHealthcheck del servicio Nest
POST/api/usersRegistro con PIN → sesión JWT
POST/api/auth/loginLogin por PIN → token + expires_at

Auth (protegidas)

MétodoPathNotas
POST/api/auth/logoutRevoca sesión
GET/api/auth/meUsuario actual

Tareas (protegidas)

MétodoPathNotas
GET/api/tasksLista del usuario autenticado
POST/api/tasksCrear tarea
POST/api/tasks/bulkCarga masiva
PUT/api/tasks/:idActualizar (ownership)
DELETE/api/tasks/:idBorrar una
DELETE/api/tasksBorrar todas del usuario

Ownership

Las mutaciones validan user_id === auth.userId. No hay multi-tenant organizacional: cada PIN/usuario es un namespace de tareas.

06-auth.md

Modelo de autenticación

Flujo

  1. El usuario crea cuenta con un PIN de 8 dígitos (Options en la PWA).
  2. El servidor hashea el PIN (bcryptjs + PIN_PEPPER) y guarda pin_hash / pin_lookup.
  3. Se crea una fila en sessions (UUID) y se emite un JWT (jose, HS256) con claims sub (userId) y sid (sessionId).
  4. El cliente guarda el token y lo envía en Authorization: Bearer ….
  5. Logout revoca la sesión; TTL configurable (SESSION_TTL_SECONDS, default 24h).

Seguridad

  • Rate-limit en endpoints de auth (por IP, en memoria por instancia de función).
  • DATABASE_URL nunca se expone al browser.
  • PIN no se almacena en claro.

Por qué PIN y no email/password

Onboarding en segundos para uso personal multi-dispositivo, sin buzones ni reset de contraseña, manteniendo hash + pepper + rate-limit.

07-data-model.md

Modelo de datos (Neon Postgres)

Tablas

users

  • id BIGSERIAL PK
  • pin_hash TEXT NOT NULL
  • pin_lookup TEXT (índice único parcial)
  • created_at timestamptz

tasks

  • id BIGSERIAL PK
  • user_id BIGINT FK → users ON DELETE CASCADE
  • title TEXT NOT NULL
  • description TEXT
  • done BOOLEAN DEFAULT false
  • priority TEXT DEFAULT medium CHECK (low|medium|high)
  • tags TEXT[] DEFAULT {}
  • created_at / updated_at (trigger handle_updated_at)

sessions

  • id UUID PK
  • user_id BIGINT FK
  • expires_at / revoked_at / created_at

Índices

  • tasks(user_id), tasks(done), tasks(priority)
  • GIN en tasks(tags)
  • tasks(created_at DESC)
  • sessions(user_id), sessions(expires_at)

Migraciones

SQL versionado en el repo: neon-migration.sql, neon-auth-migration.sql.

08-deploy-vercel.md

Deploy en Vercel (un solo proyecto)

Idea

  • apps/web → estáticos en CDN (apps/web/www)
  • apps/api → función serverless vía api/index.js (incluye apps/api/dist/**)
  • /api/* reescrito a la función; el resto a la SPA

Config (vercel.json)

  • buildCommand: npx turbo run build --filter=@task-cloud/web --filter=@task-cloud/api
  • outputDirectory: apps/web/www
  • functions.api/index.js.maxDuration: 30
  • Rewrites: /api/:path* → /api/index; resto → /index.html

Variables de entorno

NombreRequeridoNotas
DATABASE_URLsíConnection string Neon
JWT_SECRETsíopenssl rand -hex 32
PIN_PEPPERsíopenssl rand -hex 32
SESSION_TTL_SECONDSnodefault 86400
API_BASE_URLnodefault /api (same origin)

Smoke

  1. GET https://task-manager-cristiancode.vercel.app/api/health
  2. Abrir la web → Options → registrar PIN → crear/sync tareas

Local

npm install
cp .env.example .env   # DATABASE_URL, JWT_SECRET, PIN_PEPPER
npm run api            # :3001
npm start              # :4200 con proxy /api

09-decisions.md

Decisiones

Neon + Nest en lugar de Supabase client

La nube es Postgres en Neon y una API Nest propia: SQL y esquema controlados, secretos solo en servidor, mismo patrón que el resto del portfolio. Se dejó el acoplamiento a Supabase Auth/Realtime porque el producto solo necesita PIN, sesiones y tareas.

Turborepo monorepo + un deploy

Web y API comparten lockfile, turbo cache y un solo pipeline Vercel. Evita dos proyectos desincronizados y simplifica same-origin /api.

Offline primero, sync opcional

Las features core de tareas no requieren API; la nube es continuidad multi-dispositivo, no el camino crítico de cada tap.

PIN en vez de email/password

Onboarding en segundos para uso personal, sin buzones ni flujos de reset, manteniendo hash + pepper + rate-limit.

Angular 22 con Zone (no zoneless) por Ionic

La skill Angular 22 pide zoneless, [formField] en todos los controles y View Transitions del router. Ionic 8.8 no lo permite: sigue pidiendo zone.js, sus ion-* no implementan FormValueControl y la navegación va por ion-router-outlet. Se adoptan Signal Forms + httpResource donde el DOM es nativo/Material; se documenta el techo en 11-angular-skill.md.

10-challenges-results.md

Retos y aprendizajes

Serverless Nest en Vercel

Adapter Express, cold start, maxDuration y reutilizar la app entre invocaciones warm (api/index.js).

Convergencia local ↔ nube

Mapear ids locales tras upload, bulk sync y no pisar ownership entre usuarios.

Env dual (web + api)

set-env.js genera environment.*.local.ts desde el .env raíz; secretos nunca van al bundle del browser.

Resultados

  • Monorepo operativo con CI (lint, tests web, build API) en GitHub Actions.
  • PWA en producción en Vercel (estáticos + API Nest en /api) sobre Neon.
  • API con health, auth PIN/JWT, CRUD/bulk de tareas y esquema Neon versionado en SQL.
  • Código abierto como referencia Angular/Ionic + Nest + Neon + Turborepo + Vercel.

11-angular-skill.md

Skill Angular

La guía de estilo Angular de Task Cloud está en .claude/skills/angular/.

ArchivoContenido
SKILL.mdDefaults Angular 22 + house style
style-standards.mdSignals first, visibilidad, const, spread, early returns
signals.mdcomputed, effect, resource/httpResource, zoneless
signal-forms.mdSignal Forms (estable en v22)
components.mdStandalone, @if/@for, SSR, Vitest
animations.mdanimate.enter / View Transitions

La skill pide mirar el major del repo y no mezclar eras. Task Cloud está en Angular 22.1 + Ionic 8.8.18 + TypeScript 6.0. v22 hace zoneless el default en apps nuevas; esta app declara provideZoneChangeDetection() porque Ionic 8 sigue apoyándose en Zone.js.

Revisión 2026-08-13 (Angular 22)

Hallazgos aplicados:

  1. Upgrade a Angular 22.1.1 + Material/CDK 22.1 + CLI 22.1. TypeScript 6.0.3. Ionic 8.8.18 (peer >=16).
  2. Signal Forms en crear y editar tarea: form() + FormField + required/maxLength + submit(). El modelo es un signal plano (taskModel / editModel). ion-select y mat-select escriben el modelo (no tienen CVA de Signal Forms).
  3. httpResource para lecturas GET: cloudTasks (GET /tasks) y meResource (GET /auth/me), disparados por session. Mutaciones (POST/PUT/DELETE) siguen en HttpClient; tras mutar se llama cloudTasks.reload().
  4. HTTP Fetch, @if, OnPush y Zone explícito se mantienen.

Qué Angular 22 no se puede (o no se debe) aplicar por Ionic 8

Ionic 8.8 declara zone.js como peer y sus web components (ion-select, overlays, ion-router-outlet, tabs) no implementan las APIs signal-first de v22. Esto no es deuda de la skill: es un techo del runtime Ionic. No forzar estas APIs hasta que Ionic las soporte.

API Angular 22Por qué Ionic la bloqueaQué hacemos aquí
Zoneless (provideZonelessChangeDetection(), quitar zone.js)Peer oficial zone.js >= 0.13. Eventos, overlays y navegación de Ionic siguen notificando la vista vía Zone. Sin Zone, taps y modales se quedan mudos.provideZoneChangeDetection() + zone.js
[formField] en controles Ionicion-select, ion-checkbox, ion-toggle, ion-input no implementan FormValueControl. Signal Forms solo enlaza nativos / Material.form() + [formField] en matInput; ion-select / mat-select escriben el signal del modelo
View Transitions del router AngularLa navegación real pasa por ion-router-outlet + IonicRouteStrategy, no por las transiciones del Router de Angular.Transiciones de Ionic; no withViewTransitions()
Angular ARIA en overlaysAlertas, popovers y action sheets son de Ionic. Sustituirlos por Angular ARIA rompe el look y el focus trap nativo.Overlays Ionic; ARIA solo si se añade un control nuevo no-Ionic
SSR / hidrataciónPWA + Capacitor + IonicStorage son cliente. No hay servidor de render para ion-*.SPA + service worker

No atribuir a Ionic (queda fuera por el builder/legado, no por el peer):

  • Vitest — el builder webpack/Karma de esta app
  • Shell NgModule (IonicModule.forRoot(), TabsModule) — Ionic 8 sí tiene provideIonicAngular(); no se ha migrado el andamiaje de tabs

Revisar esta tabla al subir de major de Ionic. Si el peer deja de pedir zone.js y los ion-* implementan FormValueControl, se puede zoneless + [formField] en selects.

House style (extracto)

  • Estado y derivación en signal / computed / linkedSignal / httpResource. RxJS solo para streams reales; al entrar, toSignal().
  • private readonly por defecto; protected solo si la plantilla lo lee.
  • const para locales; nunca var.
  • Arrays/objetos por spread; nunca push/splice sobre estado compartido.
  • Guards arriba, happy path plano.
  • En código nuevo: standalone, inject(), input()/output(), Signal Forms, httpResource para lecturas.

Referencia en el repo de la app

Los agentes que lean .claude/skills/angular/SKILL.md aplican estas reglas al tocar apps/web.

On this page

Visión general Enlaces Stack Producto y features Problema Solución Características CRUD de tareas Prioridad y tags Filtros por estado Offline-first Sync unitario y bulk Auth PIN + sesión PWA instalable i18n Arquitectura Diagrama lógico Capas Principios Monorepo Turborepo Scripts raíz Workspaces Superficie API Nest Rutas públicas Auth (protegidas) Tareas (protegidas) Ownership Autenticación PIN + JWT Flujo Seguridad Por qué PIN y no email/password Modelo de datos Neon Tablas users tasks sessions Índices Migraciones Deploy Vercel (Turborepo) Idea Config (vercel.json) Variables de entorno Smoke Local Decisiones de producto y tech Neon + Nest en lugar de Supabase client Turborepo monorepo + un deploy Offline primero, sync opcional PIN en vez de email/password Angular 22 con Zone (no zoneless) por Ionic Retos y resultados Serverless Nest en Vercel Convergencia local ↔ nube Env dual (web + api) Skill Angular Revisión 2026-08-13 (Angular 22) Qué Angular 22 no se puede (o no se debe) aplicar por Ionic 8 House style (extracto) Referencia en el repo de la app

0% read

folder_zip Download all (ZIP)
Hermes Agent