{"id":50497633,"url":"https://github.com/code3743/todo-list","last_synced_at":"2026-06-02T09:02:59.969Z","repository":{"id":357881876,"uuid":"1238263295","full_name":"code3743/todo-list","owner":"code3743","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-14T16:34:58.000Z","size":21106,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-14T18:29:37.919Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/code3743.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-14T01:03:23.000Z","updated_at":"2026-05-14T16:35:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/code3743/todo-list","commit_stats":null,"previous_names":["code3743/todo-list"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/code3743/todo-list","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/code3743%2Ftodo-list","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/code3743%2Ftodo-list/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/code3743%2Ftodo-list/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/code3743%2Ftodo-list/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/code3743","download_url":"https://codeload.github.com/code3743/todo-list/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/code3743%2Ftodo-list/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33814319,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-02T09:02:59.014Z","updated_at":"2026-06-02T09:02:59.964Z","avatar_url":"https://github.com/code3743.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Todo List — Ionic + Angular + Firebase\n\nAplicación móvil de lista de tareas construida con Ionic, Angular y Cordova. Permite gestionar tareas y categorías con almacenamiento local o Firebase Firestore, e incluye un feature flag con Firebase Remote Config.\n\n## Stack\n\n- **Ionic** 8 + **Angular** 20 (standalone components)\n- **Cordova** — compilación nativa Android e iOS\n- **Firebase** 12 (Firestore + Remote Config)\n- **Angular Signals** — gestión de estado reactivo\n\n---\n\n## Funcionalidades\n\n- Agregar, completar y eliminar tareas\n- Crear, editar y eliminar categorías con color\n- Asignar una categoría a cada tarea\n- Filtrar tareas por categoría\n- **Feature flag** vía Firebase Remote Config: activa/desactiva la sección de categorías en tiempo real sin redesplegar la app\n- Datasource intercambiable: `local` (localStorage) o `firebase` (Firestore) con un solo cambio en `environment.ts`\n\n---\n\n## Requisitos previos\n\n- Node.js ≥ 18\n- npm ≥ 9\n- Ionic CLI: `npm install -g @ionic/cli`\n- Cordova CLI: `npm install -g cordova`\n- **Android:** Android Studio con un AVD configurado\n- **iOS:** Xcode 15+ (solo macOS)\n\n---\n\n## Instalación\n\n```bash\ngit clone https://github.com/code3743/todo-list.git\ncd todo-list\nnpm install\n```\n\n---\n\n## Ejecutar en web\n\n```bash\nionic serve\n```\n\nAbre `http://localhost:8100` en el navegador.\n\n---\n\n## Compilar y ejecutar en iOS\n\n```bash\nionic build\ncordova run ios --emulator\n```\n\nO para abrir en Xcode y seleccionar el simulador manualmente:\n\n```bash\nionic build\ncordova prepare ios\nopen platforms/ios/*.xcworkspace\n```\n\n---\n\n## Compilar y ejecutar en Android\n\n```bash\nionic build\ncordova run android --emulator\n```\n\nO para abrir en Android Studio:\n\n```bash\nionic build\ncordova prepare android\n# Abre la carpeta platforms/android en Android Studio\n```\n\n---\n\n## Configuración de Firebase\n\nLas credenciales de Firebase están en `src/environments/environment.ts` y `environment.prod.ts`. Para usar tu propio proyecto:\n\n1. Crea un proyecto en [Firebase Console](https://console.firebase.google.com)\n2. Registra una app Web\n3. Copia el `firebaseConfig` en ambos archivos de environment\n4. En Firestore, crea las colecciones `tasks` y `categories`\n5. En Remote Config, crea el parámetro `enable_categories` (tipo Boolean)\n\n### Cambiar datasource\n\nEn `src/environments/environment.ts`:\n\n```ts\ndatasource: 'local'    // usa localStorage (default en desarrollo)\ndatasource: 'firebase' // usa Firestore\n```\n\n`environment.prod.ts` usa `firebase` por defecto.\n\n### Feature flag — demostración\n\n1. Ve a Firebase Console → **Remote Config**\n2. Cambia `enable_categories` a `true` y publica\n3. Recarga la app — aparece el botón de categorías y los filtros\n4. Cambia a `false` y publica — la funcionalidad desaparece sin recompilar\n\n![Demo](/output.gif)\n\n[Demo en video](/demo.mp4)\n\n---\n\n## Arquitectura\n\n```\nsrc/app/\n├── core/\n│   ├── constants/       # Todas las claves y constantes centralizadas\n│   ├── datasources/     # Abstracción del origen de datos\n│   │   ├── tasks-datasource.ts        # Contrato abstracto\n│   │   ├── categories-datasource.ts\n│   │   ├── local/       # Implementación localStorage\n│   │   └── firebase/    # Implementación Firestore\n│   ├── models/          # Interfaces Task y Category\n│   └── services/        # Firebase, RemoteConfig, Storage\n├── features/\n│   ├── tasks/           # Componentes, servicio y pipes de tareas\n│   └── categories/      # Componentes, servicio y pipes de categorías\n└── shared/              # Componentes y pipes reutilizables\n```\n\nEl datasource activo se inyecta en `main.ts` según `environment.datasource`, sin que los servicios de negocio (`Tasks`, `Categories`) conozcan el origen de los datos.\n\n---\n\n## Respuestas técnicas\n\n### ¿Cuáles fueron los principales desafíos?\n\n**Datasource intercambiable con Firebase y localStorage:** el reto principal fue diseñar una abstracción que soportara las operaciones síncronas del localStorage y las asíncronas de Firestore con la misma interfaz. Se resolvió con clases abstractas como tokens de inyección de Angular y métodos `Promise`-based en ambas implementaciones.\n\n**Sincronía entre signals y operaciones async:** Angular Signals son síncronos, pero las operaciones de Firebase no. Se adoptó el patrón de *optimistic update*: la señal se actualiza inmediatamente en memoria y la operación de persistencia ocurre en paralelo, logrando una UI sin latencia percibida.\n\n---\n\n### ¿Qué técnicas de optimización de rendimiento aplicaste y por qué?\n\n**`ChangeDetectionStrategy.OnPush` en todos los componentes** — Angular solo recalcula la vista cuando cambia una referencia de input, reduciendo los ciclos de detección de cambios en listas largas.\n\n**Angular Signals** — reemplazan `BehaviorSubject` y `async pipe`. Las señales actualizan solo el nodo del DOM que depende de ese valor, sin recorrer el árbol de componentes.\n\n**`computed()` para el filtrado de tareas** — la lista filtrada se recalcula solo cuando cambia `tasks` o `selectedCategoryId`, no en cada render. Equivale a memoización automática.\n\n**`PreloadAllModules`** — precarga los módulos lazy en segundo plano tras el arranque inicial, eliminando el delay de navegación entre rutas.\n\n**Optimistic updates** — la UI responde de forma inmediata sin esperar la confirmación de Firebase, haciendo la app sentir nativa.\n\n---\n\n### ¿Cómo aseguraste la calidad y mantenibilidad del código?\n\n**Patrón datasource con clases abstractas** — los servicios de negocio (`Tasks`, `Categories`) dependen de contratos, no de implementaciones concretas. Agregar un nuevo backend (ej. SQLite) no requiere tocar la lógica de negocio.\n\n**Constantes centralizadas** — todas las claves de Firestore, localStorage y Remote Config viven en `app.constants.ts`. Un cambio de nombre se hace en un solo lugar.\n\n**Componentes de responsabilidad única** — `TaskItemComponent` solo renderiza, `Tasks` service solo gestiona estado, `LocalTasksDataSource` solo persiste. Cada pieza es testeable de forma aislada.\n\n**TypeScript estricto** — sin `any` explícito, tipado de modelos con interfaces, `as const` para objetos de configuración. El compilador detecta errores de contrato antes de tiempo de ejecución.\n\n**Estructura por features** — `tasks/` y `categories/` son módulos autocontenidos con sus componentes, servicios y pipes. Escalar o eliminar una feature no afecta el resto de la app.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcode3743%2Ftodo-list","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcode3743%2Ftodo-list","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcode3743%2Ftodo-list/lists"}