01-overview.md
Visión general · Lista de la compra
Lista de la compra (repo listacompra) es una Progressive Web App de lista de supermercado: despensa, lista pendiente y urgente. Funciona offline con SQLite en el navegador (sql.js + IndexedDB) y, si el usuario abre sesión, sincroniza a Neon Auth + Data API.
Es una sola app Angular/Ionic (no monorepo Nest). En producción Netlify sirve www/ y hace proxy same-origin de /__neon-auth/* y /__neon-data/* para que la cookie sea first-party (iOS Safari / PWA).
Enlaces
- Web: https://lalistadelacompra.netlify.app
- GitHub: Criscode2022/listacompra
- CI: GitHub Actions
ci.yml
Stack
| Capa | Tecnología |
|---|---|
| Web | Angular 17.3, Ionic 7, Capacitor 5, Tailwind 3, Signals |
| Offline | sql.js (SQLite WASM) persistido en IndexedDB (listacompra.sqlite) |
| Nube | Neon Auth + Neon Data API (@neondatabase/neon-js) |
| Extra | jsPDF (export de la lista), Material snackbars |
| Deploy | Netlify (www/ + SPA rewrite + proxy /__neon-*) |
02-product.md
Producto y features
Problema
Las listas del súper viven en notas, WhatsApp o papel. Se pierden las cantidades, no hay urgentes separados, y al llegar a la tienda no hay un PDF imprimible agrupado por pasillo. Las apps de recetas o de supermercado piden cuenta desde el primer tap.
Solución
PWA instalable con tres tabs: Despensa (inventario), Lista (pendientes + PDF) y Urgente. Offline-first en SQLite. Sync opt-in con Neon Auth (email verificado) y Data API.
Características
Despensa
Inventario completo. FAB abre el modal de alta (nombre, cantidad, unidad, categoría). Checkbox marca comprado.
Lista
Solo productos pendientes. Chips de categoría con contador, búsqueda, checkbox y exportar PDF (jsPDF, agrupado por categoría, con fecha).
Urgente
Misma mecánica, filtrada a urgent: true. FAB rojo para crear ya marcado como urgente.
Categorías y unidades
Once categorías (frutas, verduras, carnes, pescados, lácteos, panadería, bebidas, limpieza, higiene, congelados, otros) y unidades ud | kg | g | l | ml | oz | lb.
Ajustes
Modo básico (oculta categoría), modo online/offline, cuenta Neon, vaciar almacenamiento.
Offline-first
Sin red se puede crear, tachar y exportar. La nube es continuidad entre dispositivos.
Android
Capacitor 5 (npx cap sync android) sobre el mismo www/.
03-architecture.md
Arquitectura
Diagrama lógico
Browser / PWA Ionic (Angular 17)
│ offline: sql.js → IndexedDB
│ online + sesión: Neon JS client
▼
Same-origin /__neon-auth/* y /__neon-data/*
│ (Netlify reverse proxy)
▼
Neon Auth + Neon Data API ──► Postgres (proyecto icy-silence-71895789)
Capas
- Cliente — Tabs Ionic: Despensa, Lista, Urgente. Settings (FAB) y Auth fuera de la tab bar. Servicios:
DataService,SqliteService,NeonService,CloudSyncService,AppModeService. - Offline — SQLite en memoria (
sql.js); tras cada write se exporta el fichero a IndexedDB (listacompra.sqlite). Verdocs/SQLITE.md. - Nube —
@neondatabase/neon-js: Auth email/password y Data API de productos + settings. No hay Nest propio. - Deploy — Netlify sirve
www/y reescribe/__neon-*al host Neon.
Flujo de estado
SqliteService.open()restaura el DB file.DataServicecargaproductsybasicModeen signals.- Un
effectpersiste cada cambio y disparasyncCallbacksi hay sesión. - Las tabs leen
dataService.products()y filtran (pendiente / urgente / categoría).
04-monorepo.md
Estructura (app única)
Lista de la compra no es un monorepo Turborepo: un solo paquete Angular/Ionic.
| Path | Rol |
|---|---|
src/app/tabs/tab-pantry | Tab Despensa |
src/app/tabs/tab-list | Tab Lista + PDF |
src/app/tabs/tab-urgent | Tab Urgente |
src/app/layout/add-product-modal | Alta de producto |
src/app/layout/header | Cabecera compartida |
src/app/settings | Ajustes |
src/app/auth | Sign-in / sign-up |
src/app/core/services/data-service | Signals + persistencia |
src/app/core/services/sqlite | sql.js + IndexedDB |
src/app/core/services/neon | Cliente Auth/Data |
src/app/core/services/cloud-sync | Push/pull |
src/assets/sql-wasm.* | Runtime SQLite WASM |
docs/SQLITE.md | Cómo se guarda el .sqlite |
netlify.toml | Build + proxy Neon + SPA |
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)
| Prefijo | Destino (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) |
environment.prod.ts usa neonAuthUrl: '/__neon-auth' y neonDataApiUrl: '/__neon-data'. Proyecto Neon: icy-silence-71895789.
En desarrollo
environment.ts apunta a las URLs absolutas de Neon (sin proxy).
Operaciones de producto
CloudSyncService sube y baja:
- Filas de
products(nombre, checked, quantity, urgent, unit, category) poruser_id - Settings (
basic_mode)
Tras registro se sube lo local. Tras login, la nube reemplaza lo del dispositivo.
06-auth.md
Modelo de autenticación
Flujo
- En Ajustes, el usuario activa modo online →
/auth. - Crea cuenta (nombre, email, contraseña) o inicia sesión.
- Neon Auth (
client.auth.signUp.email/signIn.email). - Verificación de email antes de poder entrar.
getSession()calienta la caché.OnlineAuthGuarddeja pasar si el modo es offline.- Logout:
signOut(). El SQLite local se conserva.
Guards
OnlineAuthGuard— siisOnline()y no hay sesión →/auth.GuestAuthGuard— si ya hay sesión, saca al usuario de/auth.
Seguridad
- Contraseña y tokens los gestiona Neon Auth.
- Proxy same-origin para no perder la sesión por ITP.
- Cloud sync opt-in.
07-data-model.md
Modelo de datos
Offline (SQLite / sql.js)
Tablas en el fichero listacompra.sqlite (IndexedDB):
| Tabla | Contenido |
|---|---|
products | name, checked, quantity, urgent, unit, category |
app_settings | clave/valor (basicMode) |
Tipo (src/app/core/types/product.ts)
interface Product {
name: string;
checked: boolean;
quantity: number;
urgent: boolean;
unit: MeasureUnit; // ud | kg | g | l | ml | oz | lb
category: ProductCategory;
}
ProductCategory: frutas, verduras, carnes, pescados, lácteos, panadería, bebidas, limpieza, higiene, congelados, otros.
La clave de producto en UI es el nombre (toggleStatus(productName), delete(productName)).
Nube
Mismas columnas + user_id. Settings: basic_mode.
08-deploy-vercel.md
Deploy en Netlify
Idea
- Build Angular →
www/ - Catch-all SPA:
/*→/index.html200 - Proxy Neon antes del catch-all
Config (netlify.toml)
[build]
publish = "www"
command = "npm run build"
[[redirects]]
from = "/__neon-auth/*"
to = "https://<host>.neonauth.../neondb/auth/:splat"
status = 200
force = true
[[redirects]]
from = "/__neon-data/*"
to = "https://<host>.apirest.../neondb/rest/v1/:splat"
status = 200
force = true
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Live: lalistadelacompra.netlify.app.
Android
npm run build
npx cap sync android
npx cap open android
webDir de Capacitor apunta a www/.
09-decisions.md
Decisiones
SQLite en el navegador, no JSON suelto
sql.js da tablas y queries reales (ORDER BY name COLLATE NOCASE) y un único fichero exportable. Ionic Storage se usó al principio; el servicio actual persiste el .sqlite en IndexedDB.
Neon Auth/Data en lugar de Nest propio
El contrato (email, sesión, filas por usuario) cabe en Neon. Un segundo servicio no aportaba ownership custom como el PIN de Task Cloud.
Proxy Netlify same-origin
Sin proxy, la cookie de Auth es third-party y iOS/PWA la tiran.
Tres tabs, no una lista infinita
Despensa / Lista / Urgente es el modelo mental del súper: lo que hay, lo que falta, lo que no puede esperar. El PDF sale de la Lista (pendientes), no de la despensa.
Signals + effect para persistir
Cada mutación actualiza products; el effect escribe SQLite y agenda sync. Evita save() manual en cada tap.
10-challenges-results.md
Retos y aprendizajes
No guardar vacío al arrancar
DataService tiene ready = false hasta el primer load(). Sin eso, el effect de signals persistiría [] y borraría la despensa en cada refresh.
Cookie de sesión en PWA iOS
Mismo problema que Presencia: ITP. Solución: rewrite /__neon-* same-origin.
Convergencia local ↔ nube
Tras login, la nube sustituye lo local. Tras registro, se sube lo que ya había. Evita merges de dos despensas distintas.
WASM de SQLite en el bundle
sql-wasm.js + sql-wasm.wasm viven en src/assets/ para que el Service Worker y Netlify los sirvan como estáticos. Sin eso, sql.js no arranca offline.
Resultado
PWA usable en el pasillo sin red, PDF para quien aún imprime la lista, y cuenta opcional si hay dos dispositivos en casa.
11-angular-skill.md
Angular en Lista de la compra
App Angular 17.3 + Ionic 7 + TypeScript 5.4 + Zone. Estado con Signals (products, basicMode, filtros de tab). No hay skill Angular versionada en este repo.
Qué se aplica
| Práctica | Dónde |
|---|---|
Signals + effect | DataService persistencia automática |
| Computed en tabs | pendingProducts(), categoryCounts(), textFilter() |
| Modal Ionic | AddProductModalComponent |
| Guards | OnlineAuthGuard, GuestAuthGuard |
| Service worker | PWA (ngsw-config.json) |
| Tests | Karma + Jasmine (npm run test:ci) |
Techo de esta era
Ionic 7 + Angular 17: Zone obligatorio, sin Signal Forms de v21+. Los formularios del modal son ngModel. Subir a Angular 21/22 exigiría alinear Ionic (ver techo v22×Ionic en Task Cloud / Presencia).