NFCompra es un MVP online para una persona. Incluye una API Worker/D1, una PWA y una aplicacion Android; los tres clientes usan el contrato /v1. La API productiva verificada vive en https://api.nfcompra.esgarpe.dev.
- Node.js LTS y npm.
- Android Studio, JDK 21 y Android SDK disponible mediante
ANDROID_HOMEpara compilar Android.
Instala las dependencias desde la raiz:
npm installInicia la API local en un terminal:
npm run api:devInicia la PWA en otro terminal:
npm run web:devEl servidor de desarrollo de la PWA reenvia /v1 a http://localhost:8787, por lo que las rutas autenticadas y las listas usan la API local. GET /health responde 200 con { "status": "ok" } en la API local.
NFCompra usa Deployment Impact para decidir si un cambio afecta Web, API o Android:
npm run deploy:impact
npm run deploy:impact -- --format jsonLos cambios user-facing se documentan en .changes/pending/*.json, y Android calcula su siguiente version con:
npm run android:release-planEl manual completo de workflows, Vercel, Cloudflare, Android signing, changesets y troubleshooting esta en docs/deployment.md.
Para preparar un catalogo propio desde un JSON local de productos:
cd apps/api
npm run catalog:build-sql -- ../../catalogo-productos.json ../../catalog-import.sql
npx wrangler d1 execute DB --local --file ../../catalog-import.sqlEl repositorio incluye seeds sin acentos ni mojibake:
apps/api/catalog/supermercados-espana.seed.json: base propia inicial.apps/api/catalog/mercadona.seed.json: catalogo publico Mercadona transformado desde el dataset MITdatania/mercadona-catalog, con 4.358 productos, 26 categorias principales y 2.840 aliases.apps/api/catalog/nfcompra-demo.seed.sql: datos pequenos de prueba para autocompletado local, con la categoriaPruebas NFCompray productosLeche demo NFC,Pan demo NFC,Atun demo NFCyManzana demo NFC.
Para cargar un seed en la D1 local:
cd apps/api
npm run catalog:build-sql -- catalog/supermercados-espana.seed.json catalog/supermercados-espana.seed.sql
npx wrangler d1 execute DB --local --file catalog/supermercados-espana.seed.sqlPara cargar solo los datos pequenos de prueba:
cd apps/api
npx wrangler d1 execute DB --local --file catalog/nfcompra-demo.seed.sqlPara D1 remoto, genera el SQL sin transaccion SQL explicita:
cd apps/api
npm run catalog:build-sql -- catalog/supermercados-espana.seed.json catalog/supermercados-espana.seed.sql --no-transaction
npx wrangler d1 execute nfcompra-production --remote --config wrangler.production.jsonc --file catalog/supermercados-espana.seed.sqlPara previsualizar el borrado administrativo de una cuenta en D1 local:
npm run admin:delete-user -- usuario@example.com --dry-runEl borrado real pide confirmacion interactiva y usa D1 local por defecto. Para produccion hay que indicar explicitamente --remote; revisa siempre el dry-run antes de mutar.
Para regenerar el seed Mercadona desde un snapshot local del dataset:
cd apps/api
npm run catalog:build-mercadona -- catalog/mercadona.seed.json --dataset-dir ../../.tmp/mercadona-catalogEl importador acepta campos genericos como name, category, brand, packageSize, sourceProductId y aliases. Tambien reconoce exportaciones JSON con results[], display_name, category_name y price_instructions.unit_size. No descarga imagenes ni precios. Antes de generar SQL, repara mojibake comun y guarda texto visible sin acentos.
Android usa https://api.nfcompra.esgarpe.dev/ como base de API por defecto en debug y release, por lo que la APK debug instalada en un movil real conecta con produccion. Para pruebas locales en emulador se puede sustituir mediante la propiedad de Gradle NFCompraApiBaseUrl=http://10.0.2.2:8787/ o la variable de entorno NFCOMPRA_API_BASE_URL.
Desde la raiz, para la API:
npm run api:test && npx --workspace @nfcompra/api tsc --noEmitPara la PWA:
npm --workspace @nfcompra/web run test
npm --workspace @nfcompra/web run typecheck
npm --workspace @nfcompra/web run buildDesde apps/android, con ANDROID_HOME apuntando al SDK instalado, para las pruebas unitarias de la base Room, autenticacion, listas y colaboracion, la compilacion de las pruebas Compose y el APK de depuracion:
.\gradlew.bat :core:database:testDebugUnitTest :core:network:testDebugUnitTest :feature:auth:testDebugUnitTest :feature:shoppinglist:testDebugUnitTest :feature:sharing:testDebugUnitTest :feature:shoppinglist:compileDebugAndroidTestKotlin :feature:sharing:compileDebugAndroidTestKotlin :app:assembleDebugEl APK debug verificado queda en apps/android/app/build/outputs/apk/debug/app-debug.apk. La APK firmada para releases se genera desde apps/android con:
.\gradlew.bat :app:assembleReleaseEl APK firmado queda en apps/android/app/build/outputs/apk/release/app-release.apk y se publica en GitHub Releases como NFCompra-release.apk para que la web y el autoupdater Android puedan descargarlo.
- La API permite registro, verificacion y reenvio de verificacion de correo, inicio y cierre de sesion, renovacion, recuperacion y restablecimiento de contrasena, consulta y edicion de perfil, cambio autenticado de contrasena y borrado de cuenta con confirmacion de contrasena actual en
DELETE /v1/me. El registro acepta nombre, apellidos, fecha de nacimiento, username, email y password; la contrasena se guarda solo como hash. La recuperacion de contrasena conserva el enlace con token y anade un codigo OTP de 6 cifras para la app; ambos se guardan solo como hashes, caducan a los 30 minutos, se consumen una sola vez y el OTP queda bloqueado tras 5 intentos fallidos. Los correos de verificacion y recuperacion se envian con HTML de marca, boton principal y enlace manual; el de recuperacion muestra tambien el bloque de codigo OTP. Las pruebas usan un remitente falso; no se documentan claves ni se verifica el envio de correo real. - D1 incluye la base del catalogo de productos de supermercado con
product_categories,product_catalogyproduct_aliases, ademas de la referencia opcionalshopping_items.catalog_product_id. Cada cuenta guardaproductEntryMode(catalogpor defecto oquick) mediante/v1/me; la entrada rapida persiste solo elshopping_itemconcatalog_product_id = NULL. La base productiva verificada incluye 4.386 productos, 38 categorias y 2.872 aliases, sin acentos ni mojibake en nombres visibles. La API exponeGET /v1/product-categories,GET /v1/product-catalog?search=...,GET /v1/product-catalog/versionyGET /v1/product-catalog/snapshot. La PWA descarga el snapshot compacto una vez, lo mantiene en memoria y filtra localmente para autocompletado; si el snapshot falla, conserva la busqueda remota como fallback. El mismo contrato queda preparado para cache local en Android con Room. El repositorio no incluye scraping en directo. - Una persona puede crear varios hogares manualmente; al crear un hogar no se crea ninguna lista automaticamente. Cada hogar puede quedarse sin listas y muestra un estado vacio hasta que se cree una manualmente. Los propietarios pueden renombrar y eliminar hogares desde la lista de hogares. Las listas se pueden crear, renombrar, eliminar y vaciar; la purga elimina solo productos comprados. Las mutaciones de productos requieren autenticacion y cubren alta, edicion, marcado, borrado, purga, busqueda normalizada y conflictos de version. Un
operationIdcompletado reproduce su respuesta y una operacion pendiente devuelve409 OPERATION_IN_PROGRESS. - Los propietarios de un hogar pueden crear, renovar, listar y revocar invitaciones en
/v1/households/:householdId/invitations, y eliminar miembros. Cualquier miembro del hogar puede consultar/v1/households/:householdId/members. Las invitaciones se aceptan mediantePOST /v1/invitations/accepto, desde una notificación,POST /v1/invitations/:invitationId/accept; ambas rutas requieren la cuenta verificada destinataria. Caducan a los siete dias y los tokens se guardan solo como hashes. - Hito 3A: la API persiste notificaciones internas en
/v1/notifications: lista reciente, contador de no leídas, marcado individual y marcado total. Las invitaciones, altas y bajas de miembros y cambios remotos de productos notifican solo a las otras personas afectadas; la actividad de una lista se agrupa por destinatario, autor, lista y tipo durante cinco minutos. No incluye notificaciones push. - La PWA muestra una landing publica en
/para visitantes anonimos, con modales accesibles para iniciar sesion y crear cuenta. La landing publica comunica que NFCompra ya dispone de PWA, app Android, catalogo con autocompletado, favoritos por usuario, notificaciones, listas fijadas, cache local y NFC vinculado a hogares; incluye una seccion especifica en castellano de aplicacion web instalable para iPhone/ordenador, mockups visuales adicionales de inicio y listas web autenticadas, y una seccion Android con vistas propias usando la marca NFCompra. El formulario de registro web recoge nombre, apellidos, fecha de nacimiento, username, email, password y confirmacion de password. Las rutas directas de registro, inicio de sesion, verificacion, reenvio, recuperacion, restablecimiento e invitaciones se mantienen mediante fallback SPA en Vercel. La pagina de verificacion permite verificar, ir a inicio de sesion y copiar el enlace completo de verificacion con un boton de icono. La pagina web de restablecimiento mantiene el enlace con token y tambien acepta email + codigo OTP cuando se abre sin token. La landing ofrece accesos diferenciados para iniciar sesion y registrarse, y comunica que cada pegatina NFC abre el hogar al que esta vinculada. Conserva el token de acceso en memoria y la API gestiona la cookie de renovacion. Permite seleccionar hogares y listas, crear hogares/listas y gestionar productos; el modo Catalogo usa el snapshot local para autocompletar y el modo Entrada rapida crea un unico candidato temporal con el texto escrito o dictado. Ambos reutilizan las vistas lista/tarjetas, cantidad y bandeja de confirmacion. El modo tarjetas muestra iconos genericos por producto, controles de cantidad desde cero, una bandeja temporal de pendientes de anadir y eliminacion previa con boton o gesto de deslizar. La lista y la cuadricula de resultados se cierran al pulsar fuera y se reabren al enfocar de nuevo el campo Producto manteniendo la busqueda escrita. Consulta la lista visible cada 15 segundos y aplica mutaciones optimistas con reintento ante conflictos. - Hito 3B (PWA): guarda en IndexedDB la ultima respuesta correcta de cada lista por usuario autenticado. Si falla la consulta sin conexion, muestra solo esa instantanea de usuario y lista en modo lectura; respuestas tardias de otra fuente no cambian ese estado. Una respuesta correcta posterior sustituye la instantanea y devuelve los controles a su modo conectado. Deshabilita las mutaciones y borra las instantaneas del usuario al cerrar sesion. No almacena tokens ni datos de invitaciones, ni encola mutaciones offline.
- Los propietarios administran miembros e invitaciones desde el hogar seleccionado. La ruta
/invitations/accept?token=...conserva su continuación de inicio de sesión ensessionStorage, muestra errores de aceptación sin datos ajenos y abre el hogar aceptado. La cabecera autenticada incluye notificaciones con contador, lectura individual o total y navegación al hogar o lista relacionada; consulta actualizaciones solo mientras el documento está visible. - La PWA conserva un aviso de error de lectura de notificaciones tras una navegacion contextual y permite cerrarlo; una solicitud de hogar/lista en URL se aplica una sola vez para no bloquear la seleccion manual posterior.
- La PWA autenticada usa una cabecera responsive con marca NFCompra, navegacion principal, notificaciones, menu de perfil accesible, pantalla de perfil editable con email de solo lectura y cambio de contrasena, y un control de descarga de APK conectado al ultimo APK publicado en GitHub Releases.
- La ruta autenticada
/muestra un panel de hogares con recuentos de miembros, listas, pendientes y progreso, actividad reciente y accesos para crear hogares/listas o abrir NFC. - La PWA autenticada incluye
/households,/households/:id,/lists,/lists/:id,/nfc,/profiley/settings. Los detalles de hogar mantienen la gestion de miembros e invitaciones; los detalles de lista reutilizan la experiencia offline y optimista existente. - La PWA autenticada muestra cabeceras y tarjetas compactas para hogares y listas; la pantalla de lista centra el nombre de la lista, permite renombrarla desde el icono de lapiz, coloca eliminar lista y vaciar comprados a la izquierda, coloca el cambio lista/tarjetas arriba a la derecha, integra la creacion de producto en la cabecera con control
- cantidad +, separa pendientes y comprados, y usa controles cuadrados para marcar, editar y eliminar productos. El modo edicion de producto mantiene solo nombre y cantidad, sin unidad, con un stepper compacto coloreado segun pendiente o comprado, y acciones explicitas para guardar o cancelar. - Android ofrece pantallas de autenticacion estilo Grocery/Big Cart con welcome, login, signup, recuperacion por email, pantalla OTP de 6 cifras y restablecimiento con nueva contrasena/confirmacion en espanol, sustituyendo la imagen original por el logo de NFCompra. El OTP se valida contra la API antes de avanzar a la pantalla de nueva contrasena, y los errores JSON de la API se muestran como mensajes legibles para el usuario. Welcome incluye acceso por email y acceso local por biometria cuando existe una sesion guardada habilitada. Los mensajes temporales de autenticacion se limpian automaticamente tras unos segundos. Los formularios de auth usan iconos Material en los campos y control de ojo para mostrar u ocultar contrasenas. El registro Android recoge nombre, apellidos, fecha de nacimiento, username, email, password y confirmacion de password, usa campos compactos con placeholder estable para que el texto no se corte al enfocar, sube el logo y amplia la tarjeta del formulario. Conserva los tokens de sesion y renovacion mediante Android Keystore. La pantalla autenticada consulta
/v1/mepara mostrar el nombre real del usuario en Inicio, Perfil permite editar nombre, apellidos y username, mantiene el email en solo lectura y cambia la contrasena desde/v1/me/change-password, muestra una animacion de carga con el logo de NFCompra mientras inicializa listas, incluye un banner superior compacto con degradado verde, titulo centrado, campana superior con panel desplegable de notificaciones y flecha de vuelta en pantallas secundarias, status bar verde, navbar inferior flotante con el mismo degradado para Inicio, Hogares, Listas, Catalogo y Perfil, contenido interno estilizado con los colores web verde oscuro, lima, fondo claro y textos oscuros, pantalla de Catalogo con categorias reales de/v1/product-categoriesen tarjetas visuales, resumen de hogares/listas, seleccion de hogares aunque no tengan listas, creacion manual de la primera lista, renombrado, vaciado total de lista, eliminacion de listas y gestion de productos, con estados de carga, error y datos, y reintento ante conflictos. - Android usa el logo de NFCompra como icono launcher normal/redondo y como imagen de la pantalla de carga inicial, con fondo claro coherente con la app.
- Android separa Inicio de la gestion de una lista concreta: Inicio muestra saludo, acciones rapidas, metricas, hogares y listas recientes, sin controles de renombrar, vaciar, eliminar ni editor de productos. La pestana Hogares reemplaza el mini-dashboard por un boton principal
+ Crear hogar, un separadorGestiona tus hogaresy tarjetas alargadas compactas/desplegables. Al desplegar un hogar se muestra su detalle, acciones para abrirlo, renombrarlo, eliminarlo y acceder a Miembros; la gestion de miembros se abre como ventana modal sobre la app y reutiliza el flujo real de invitaciones, miembros activos, revocacion de invitaciones y eliminacion de miembros para propietarios. Android conecta tambien el contratoPATCH/DELETE /v1/households/:idpara renombrar y eliminar hogares. La pestana Listas muestra el resumen de listas guardadas, coloca crear lista como accion principal a ancho completo, agrupa las listas por hogar y las presenta en tarjetas tipo catalogo con resumen de pendientes y comprados cacheado por lista visitada; cada tarjeta permite fijar una lista, abrir en modo edicion para anadir productos o abrir en modo vista supermercado sin acciones destructivas ni cabecera extra. La lista fijada se conserva en preferencias locales y aparece en Inicio como acceso rapido en la tercera tarjeta de metricas; Hogares y Listas son tarjetas interactivas. Crear lista abre un dialogo con selector de hogar y nombre. Al abrir una lista, Android navega a una pantalla independiente cuyo banner muestra el nombre editable de la lista y debajo el hogar. La cabecera interna queda compacta, con eliminar, vaciar rectangular que borra pendientes y comprados, selector unico lista/cuadricula dibujado en Compose y alta de producto con cantidad; debajo aparecen secciones visuales separadas para Pendientes y Comprados con tarjetas y colores coherentes con la PWA. El dialogo de edicion de producto usa borde amarillo o rojo segun el estado del producto y permite editar nombre y cantidad con controles-y+ampliados. La busqueda de producto replica la PWA: precarga el snapshot de catalogo en segundo plano, guarda el snapshot por cuenta en un fichero local para reutilizarlo entre cierres de app, usa busqueda remota ligera mientras no haya snapshot/cache disponible, y luego filtra localmente. Mantiene modo lista con sugerencias, modo tarjetas con cantidad inicial cero, tarjetas no seleccionables hasta elegir cantidad, iconos genericos, waitlist con boton de confirmacion y eliminacion previa por boton o gesto de deslizar. Los favoritos de producto mantienen una capa local en memoria tras guardarse en la API para que las busquedas posteriores conserven la estrella y el orden de favoritos; si la API falla, la estrella se revierte y se muestra un aviso. Al desplazar los resultados de busqueda, Android oculta el teclado y mantiene el scroll contenido en el panel de resultados. Todas las pestanas principales y las pantallas de lista rellenan la altura util con el fondo de pagina para que no aparezca una franja blanca detras del navbar flotante. El tema Android define radios globales moderados y los botones principales de Inicio, Hogares, Listas y dialogos usan forma rectangular con bordes redondeados. - El catalogo permite favoritos por usuario mediante
user_product_favorites. La API expone marcado/desmarcado en/v1/product-catalog/:productId/favorite, devuelveisFavoriteen busqueda y snapshot autenticados, ordena favoritos antes que el resto cuando coinciden con la busqueda y muestra una categoria virtualFavoritosal inicio de/v1/product-categories. El catalogo tiene dos ambitos (verdocs/security-roadmap.mdSEC-01/CAT-01): el catalogo del sistema es solo de administradores (users.role = 'admin'), mientras que cada hogar dispone de su propio catalogo personalizado (/v1/households/:householdId/product-catalogy.../product-categories) gestionado por sus miembros; la busqueda y el snapshot conhouseholdIdmezclan sistema + hogar actual sin filtrar en cliente, y los productos de hogar se distinguen en los resultados con un acento violeta y un icono de casa. Android mantiene la cabecera de busqueda fija al entrar en una categoria, cambia el+por un menu...de categoria, muestra acciones...en las tarjetas de producto y limpia la cache del snapshot tras mutaciones. - El borrado de usuarios reutiliza
AccountDeletionService: hogares propios con otros miembros transfieren propiedad al miembro activo mas antiguo porhousehold_members.created_at ASC, user_id ASC; hogares sin otros miembros se eliminan. El comando localnpm run admin:delete-user -- usuario@example.com --dry-runmuestra impacto sin cambios y el borrado real pide confirmacion. - Hito 3B (Android offline-first): Room conserva hogares, listas, productos y operaciones pendientes en una base aislada por cuenta; los tokens continúan en Android Keystore y no se guardan en la base. Las instancias del almacén de tokens de un mismo proceso comparten coordinación y generación de sesión, por lo que una operación demorada de una actividad anterior no sustituye ni borra una sesión más reciente. En un arranque con instantánea Room, la pantalla muestra hogares, listas y productos cacheados antes de esperar la actualización HTTP; una lectura cacheada tardía de un contexto anterior tampoco sustituye el hogar o la lista abiertos después. Cada mutación local añade una operación persistente y agenda trabajo único de WorkManager restringido a
NetworkType.CONNECTED. El Worker procesa una operación cada vez por orden de creación, conserva suoperationIddurante reintentos y aplica backoff exponencial ante fallos temporales. Una respuesta correcta actualiza el producto y elimina solo su operación dentro de una transacción Room; las altas reconcilian atómicamente el ID temporal, las versiones y la proyección de las operaciones posteriores. Los errores 422,OPERATION_ID_REUSED,OPERATION_LOSTy otros 409 no recuperables quedan comofailed, visibles como revisión manual, y no bloquean las operaciones siguientes; soloOPERATION_IN_PROGRESSreintenta el mismo UUID.409 ITEM_VERSION_CONFLICTconserva la versión del servidor sin descartar la intención local. La pantalla compara nombre, versión y, para toggles, estado comprado/pendiente, y permite «Usar versión del servidor» o «Reintentar mi cambio»; el reintento crea una operación nueva con otro UUID y la versión vigente del servidor. Las filassyncinginterrumpidas se reanudan con el UUID almacenado. Cada Worker rechaza credenciales de otra cuenta y usa un cliente Bearer de solo lectura: un401queda reintentable sin consumir ni rotar el refresh token de la sesión interactiva. Cerrar o recrear la interfaz libera sus colectores y su referencia a Room sin cancelar la cola; el cierre de sesión, el cambio de cuenta o la revocación detienen y esperan las operaciones del repositorio antes de cancelar por última vez el trabajo único de la cuenta anterior. - Hito 3B (integración): las regresiones cubren iniciar con datos cacheados de Room, ordenar dos mutaciones de producto offline en un único ciclo de sincronización y resolver un conflicto de versión de forma explícita.
- Android permite abrir los miembros del hogar seleccionado, invita, revoca y elimina miembros solo desde controles de propietario con confirmacion, y muestra el contenido en solo lectura al resto. La sesion autenticada incluye una campana accesible con contador, lectura individual o total y navegacion al hogar o lista exactos; consulta notificaciones cada 15 segundos mientras la actividad esta reanudada y la sesion sigue autenticada. Un fallo al marcar una notificacion no bloquea la navegacion y muestra un aviso cerrable; una lectura correcta refresca la lista y el contador.
- Android acepta tanto
https://nfcompra.esgarpe.dev/invitations/accept?token=..., que coincide con el enlace publico de correo previsto, comonfcompra://app/invitations/accept?token=.... El filtro HTTPS no declara verificacion de App Links, por lo que Android puede mostrar su selector. El token pendiente se conserva solo mediante el estado de instancia de la actividad durante una recreacion y se elimina al aceptar o cancelar; no se guarda en preferencias ni se registra. No se habilitan push ni NFC; el único trabajo en segundo plano es la sincronización de la cola offline mediante WorkManager.
- No hay despliegue ni operaciones remotas incluidas.
- La PWA no encola ni sincroniza mutaciones offline. Android limita su cola offline a mutaciones de productos; las mutaciones de listas usan el contrato HTTP conectado existente. NFC, WebSockets y notificaciones push quedan fuera de este alcance.
apps/api: Worker local, migraciones D1 y contrato/v1.apps/web: PWA React con autenticacion y listas conectadas a la API local.apps/android: aplicacion Compose con autenticacion, listas respaldadas por Room y API configurada paradebug.docs: diseno, arquitectura, contrato versionado de API y plan del MVP.