# ADMIN BULK UPDATE — TASKS.md ## Backend ### BULK-BE-001 **ID**: BULK-BE-001 **Title**: BulkOperation domain types **Goal**: Define all types for bulk operations **Why**: Shared contracts for handlers, orchestrator, and API **Dependencies**: None **Applications**: Backend **Modules**: bulk/domain/bulk-operation.ts **Database impact**: None **API contracts**: Shapes of request/response objects **Permissions**: N/A **Implementation**: Define BulkOperationType enum, BulkOperationParams union, PreviewItem, PreviewResult, ExecuteResult interfaces; ValidationResult **Tests**: Type tests (TypeScript compilation) **Expected blast radius**: Low **Definition of Done**: All types defined; TypeScript compiles without errors ### BULK-BE-002 **ID**: BULK-BE-002 **Title**: BulkOperationHandler port + handler registry **Goal**: Define the extension interface for bulk operations **Why**: Allows adding future operations without touching core orchestrator **Dependencies**: BULK-BE-001 **Applications**: Backend **Modules**: bulk/domain/ports.ts **Database impact**: None **API contracts**: BulkOperationHandler interface **Permissions**: N/A **Implementation**: Define `BulkOperationHandler` interface; create `HANDLERS` registry map; export from bulk/index.ts **Tests**: Handler lookup by type works; unknown type returns undefined **Expected blast radius**: Low **Definition of Done**: Interface exists; registry is extensible; adding a handler requires only adding to registry ### BULK-BE-003 **ID**: BULK-BE-003 **Title**: Price bulk operation handlers **Goal**: Implement SET, INCREASE_PERCENT, DECREASE_PERCENT, INCREASE_FIXED, DECREASE_FIXED **Why**: Initial price operations **Dependencies**: BULK-BE-002 **Applications**: Backend **Modules**: bulk/application/handlers/price-handlers.ts **Database impact**: None **API contracts**: None (domain) **Permissions**: N/A **Implementation**: Five handler classes; each implements validate(), preview(), buildPatch(); rounding uses Math.round(); VAT is stored as net price **Tests**: - BULK-BE-UT-001: INCREASE_PERCENT 5% on 350 cents → 368 cents - BULK-BE-UT-002: DECREASE_PERCENT 5% on 350 cents → 333 cents - BULK-BE-UT-003: INCREASE_FIXED 50 on 350 cents → 400 cents - BULK-BE-UT-004: DECREASE_FIXED 50 on 350 cents → 300 cents - BULK-BE-UT-005: DECREASE_FIXED below 0 → floor at 0 - BULK-BE-UT-006: SET_PRICE 500 → 500 - BULK-BE-UT-007: invalid percent (0, negative, >1000) → ValidationResult.valid=false - BULK-BE-UT-008: missing VAT rate → ValidationResult.valid=false - BULK-BE-UT-009: product without price → PreviewItem.status=failed, error=PRICE_NOT_FOUND **Expected blast radius**: Low **Definition of Done**: All five handlers produce correct price calculations; invalid params rejected at validate() ### BULK-BE-004 **ID**: BULK-BE-004 **Title**: Category bulk operation handlers **Goal**: Implement ADD_CATEGORY, REMOVE_CATEGORY, REPLACE_CATEGORIES **Why**: Initial category operations **Dependencies**: BULK-BE-002 **Applications**: Backend **Modules**: bulk/application/handlers/category-handlers.ts **Database impact**: None **API contracts**: None (domain) **Permissions**: N/A **Implementation**: Three handler classes; ADD deduplicates; REMOVE filters; REPLACE replaces entirely; validates categoryId exists (optional — can be deferred) **Tests**: - BULK-BE-UT-010: ADD_CATEGORY to product with 2 categories → 3 categories - BULK-BE-UT-011: ADD_CATEGORY with duplicate → no-op (deduplicated) - BULK-BE-UT-012: REMOVE_CATEGORY from product with 3 categories → 2 categories - BULK-BE-UT-013: REMOVE_CATEGORY not present → no change - BULK-BE-UT-014: REPLACE_CATEGORIES with 2 new categories → exactly those 2 - BULK-BE-UT-015: ADD without categoryId → ValidationResult.valid=false **Expected blast radius**: Low **Definition of Done**: All three handlers produce correct category patches; validation rejects invalid input ### BULK-BE-005 **ID**: BULK-BE-005 **Title**: BulkService orchestrator **Goal**: Orchestrate preview + execute; integrate with audit **Why**: Core business logic tying handlers, repository, and audit together **Dependencies**: BULK-BE-001, BULK-BE-002, BULK-BE-003, BULK-BE-004 **Applications**: Backend **Modules**: bulk/application/bulk-service.ts **Database impact**: None **API contracts**: None (service) **Permissions**: N/A **Implementation**: BulkService with preview() and execute(); execute() uses transaction; logs audit entry on execution; rollback on error; generate operationId **Tests**: - BULK-BE-UT-020: preview() calls handler.validate() and handler.preview() for each product - BULK-BE-UT-021: execute() calls handler.buildPatch() for each valid product - BULK-BE-UT-022: execute() logs one audit entry per bulk operation - BULK-BE-UT-023: execute() rollback on error → no product modified - BULK-BE-UT-024: unknown operation type → AppError(400) **Expected blast radius**: Medium — changes persistence **Definition of Done**: BulkService correctly orchestrates all operations; audit entry is complete; transaction is atomic ### BULK-BE-006 **ID**: BULK-BE-006 **Title**: BulkProductRepository — read products with pricing in batch **Goal**: Efficiently load product+price data for bulk operations **Why**: Avoids N queries for N products **Dependencies**: BULK-BE-001 **Applications**: Backend **Modules**: bulk/infrastructure/pg-bulk-repository.ts **Database impact**: None **API contracts**: None **Permissions**: N/A **Implementation**: Single SQL query to JOIN catalog_products + pricing_variants; returns ProductWithPrice[] (internal type); handles products with no price gracefully **Tests**: BULK-BE-IT-001: batch load of 20 products returns correct data; BULK-BE-IT-002: product without price returns null price field **Expected blast radius**: Low **Definition of Done**: Products loaded in one query; missing price handled gracefully ### BULK-BE-007 **ID**: BULK-BE-007 **Title**: Bulk API routes — preview and execute **Goal**: Expose bulk operations via REST API **Why**: Admin UI needs these endpoints **Dependencies**: BULK-BE-005, BULK-BE-006 **Applications**: Backend **Modules**: bulk/api/bulk.routes.ts **Database impact**: None (read for preview; write for execute) **API contracts**: POST /admin/bulk/preview, POST /admin/bulk/execute **Permissions**: admin role required on both **Implementation**: Register routes in build-app.ts; parse request body; call BulkService; serialize response; handle errors with AppError **Tests**: - BULK-BE-IT-010: POST /admin/bulk/preview with valid INCREASE_PERCENT → 200 + PreviewResult - BULK-BE-IT-011: POST /admin/bulk/preview with invalid params → 422 - BULK-BE-IT-012: POST /admin/bulk/preview without auth → 401 - BULK-BE-IT-013: POST /admin/bulk/execute with valid params → 200 + ExecuteResult - BULK-BE-IT-014: POST /admin/bulk/execute → applies changes to DB; products updated - BULK-BE-IT-015: audit log entry created after execute **Expected blast radius**: Low — new routes **Definition of Done**: Both routes respond correctly; auth enforced; changes persisted ### BULK-BE-008 **ID**: BULK-BE-008 **Title**: Wire bulk module into build-app.ts **Goal**: Register bulk routes with Fastify **Why**: Module must be mounted in the application **Dependencies**: BULK-BE-007 **Applications**: Backend **Modules**: app/build-app.ts **Database impact**: None **API contracts**: None **Permissions**: N/A **Implementation**: Import registerBulkRoutes; call in build-app.ts alongside other modules; pass pool, authenticate, audit **Tests**: Integration test that /admin/bulk/preview and /admin/bulk/execute respond **Expected blast radius**: Low **Definition of Done**: Bulk routes accessible at /admin/bulk/* ## Admin ### BULK-ADM-001 **ID**: BULK-ADM-001 **Title**: Bulk Update page shell + product selection **Goal**: Product selection step of the wizard **Why**: Admin needs to pick which products to update **Dependencies**: BULK-BE-007 **Applications**: Admin **Modules**: apps/admin/src/app/(dashboard)/bulk-update/page.tsx **Database impact**: None **API contracts**: GET /catalog/products (existing) **Permissions**: products.bulk_update **Implementation**: Full-width page with product table; checkbox column; search/filter bar; selected count badge; "Continue to Configure" button **Tests**: Products load; checkboxes select/deselect; selected count accurate; filter narrows selection **Expected blast radius**: Low **Definition of Done**: Admin can select products and see selection count ### BULK-ADM-002 **ID**: BULK-ADM-002 **Title**: Operation selector + configuration step **Goal**: Choose and configure the bulk operation type **Why**: Second step of the wizard **Dependencies**: BULK-ADM-001 **Applications**: Admin **Modules**: apps/admin/src/app/(dashboard)/bulk-update/page.tsx **Database impact**: None **API contracts**: None yet **Permissions**: products.bulk_update **Implementation**: Two cards: Price / Categories; selecting Price shows sub-options (SET, +%, -%, +FIXED, -FIXED) with parameter inputs; selecting Categories shows (ADD, REMOVE, REPLACE) with category picker; Back button returns to selection **Tests**: Selecting each operation shows correct sub-options; invalid input shows error state **Expected blast radius**: Low **Definition of Done**: Admin can select operation type and enter parameters ### BULK-ADM-003 **ID**: BULK-ADM-003 **Title**: Preview step **Goal**: Show proposed changes before committing **Why**: Critical safety step — see what will change before writing **Dependencies**: BULK-BE-007, BULK-ADM-002 **Applications**: Admin **Modules**: apps/admin/src/app/(dashboard)/bulk-update/page.tsx **Database impact**: None (dry-run) **API contracts**: POST /admin/bulk/preview **Permissions**: products.bulk_update **Implementation**: Call preview API with selected productIds, operation, parameters; display table (Product | Current | Proposed | Status); show summary bar ("18 valid, 2 failed"); Back button to Configure **Tests**: Preview loads; shows correct current→proposed values; failed rows highlighted with error reason **Expected blast radius**: Low **Definition of Done**: Preview accurately reflects what execute() would do; no product modified ### BULK-ADM-004 **ID**: BULK-ADM-004 **Title**: Confirmation + execution step **Goal**: Require explicit confirmation before executing bulk operation **Why**: Safety barrier — prevent accidental bulk changes **Dependencies**: BULK-ADM-003, BULK-BE-007 **Applications**: Admin **Modules**: apps/admin/src/app/(dashboard)/bulk-update/page.tsx **Database impact**: MODIFIES PRODUCTS **API contracts**: POST /admin/bulk/execute **Permissions**: products.bulk_update **Implementation**: Show warning message ("X changes will be applied. This cannot be undone."); [Cancel] [Apply Changes] buttons; on Apply: call execute API; on success: show result summary; on error: show error + retry option **Tests**: Confirmation dialog shows before any write; cancel returns to preview; apply calls execute; result shows success/failure counts **Expected blast radius**: HIGH — modifies products **Definition of Done**: Confirmation required; execute called only after explicit confirmation; result displayed accurately ### BULK-ADM-005 **ID**: BULK-ADM-005 **Title**: Navigation sidebar — add Bulk Update link **Goal**: Make bulk update accessible from admin navigation **Why**: Discoverability **Dependencies**: BULK-ADM-001 **Applications**: Admin **Modules**: apps/admin/src/lib/permissions.ts **Database impact**: None **API contracts**: None **Permissions**: products.bulk_update **Implementation**: Add to NAV_ITEMS: { href: '/bulk-update', label: 'Actualización masiva', icon: '📦', permission: 'products.bulk_update' } **Tests**: Bulk Update appears in sidebar for admin users **Expected blast radius**: Low **Definition of Done**: Nav item visible in admin sidebar ## QA ### BULK-QA-001 **ID**: BULK-QA-001 **Title**: Bulk update E2E tests **Goal**: End-to-end validation of complete bulk update flow **Why**: Critical safety feature — full flow must work **Dependencies**: BULK-BE-007, BULK-ADM-004 **Applications**: QA **Modules**: E2E tests **Tests**: - BULK-QA-UT-001: Select 5 products → preview shows correct proposed prices - BULK-QA-UT-002: Preview does NOT modify any product - BULK-QA-UT-003: Confirm → products actually updated in DB - BULK-QA-UT-004: Audit log entry exists for bulk operation - BULK-QA-UT-005: 5% increase on products with VAT general → correct net price applied - BULK-QA-UT-006: ADD_CATEGORY → categories added without removing existing - BULK-QA-UT-007: REMOVE_CATEGORY → category removed, others preserved - BULK-QA-UT-008: REPLACE_CATEGORIES → only new categories remain - BULK-QA-UT-009: Product without price → failed in preview and in execute (not silently skipped) - BULK-QA-UT-010: Non-admin user → 401 on /admin/bulk/execute **Expected blast radius**: N/A **Definition of Done**: All tests pass; QA sign-off obtained --- ## Task Summary Table | Task | Layer | Feature | Depends On | Risk | Parallel | |------|-------|---------|-----------|------|---------| | BULK-BE-001 | Backend | Domain types | — | Low | * | | BULK-BE-002 | Backend | Handler interface | BULK-BE-001 | Low | * | | BULK-BE-003 | Backend | Price handlers | BULK-BE-002 | Low | * | | BULK-BE-004 | Backend | Category handlers | BULK-BE-002 | Low | * | | BULK-BE-005 | Backend | BulkService orchestrator | BULK-BE-001..004 | Medium | * | | BULK-BE-006 | Backend | BulkProductRepository | BULK-BE-001 | Low | * | | BULK-BE-007 | Backend | API routes | BULK-BE-005, BULK-BE-006 | Medium | * | | BULK-BE-008 | Backend | Wire into build-app | BULK-BE-007 | Low | * | | BULK-ADM-001 | Admin | Product selection UI | BULK-BE-007 | Low | * | | BULK-ADM-002 | Admin | Operation config step | BULK-ADM-001 | Low | * | | BULK-ADM-003 | Admin | Preview step | BULK-ADM-002, BULK-BE-007 | Low | * | | BULK-ADM-004 | Admin | Confirm + execute step | BULK-ADM-003, BULK-BE-007 | Medium | * | | BULK-ADM-005 | Admin | Sidebar nav link | BULK-ADM-001 | Low | * | | BULK-QA-001 | QA | E2E tests | BULK-BE-007, BULK-ADM-004 | Medium | After all | **Parallel group**: BULK-BE-001 and BULK-BE-002 are independent; BULK-BE-003 and BULK-BE-004 are parallel (both depend on BE-002); BULK-BE-005 depends on all four handlers; BULK-BE-006 independent; BULK-BE-007 depends on BE-005 + BE-006; Admin tasks BULK-ADM-001 through BULK-ADM-005 are sequential (wizard flow). **Recommended order**: 1. BULK-BE-001 + BULK-BE-002 + BULK-BE-006 (parallel, no deps) 2. BULK-BE-003 + BULK-BE-004 (parallel, both depend on BE-002) 3. BULK-BE-005 (depends on BE-001..004) 4. BULK-BE-007 (depends on BE-005, BE-006) 5. BULK-BE-008 (depends on BE-007) 6. BULK-ADM-001 (depends on BE-007) 7. BULK-ADM-002 through BULK-ADM-004 (sequential wizard) 8. BULK-ADM-005 (parallel with any ADM task) 9. BULK-QA-001 (after everything) **High-risk tasks**: - BULK-BE-005: Transaction logic must be correct — wrong rollback = data corruption - BULK-ADM-004: Executes actual DB writes — requires confirmation UI; must not auto-submit - BULK-BE-007: New API endpoints with admin auth — verify auth enforced **MVP boundary**: BULK-BE-001 through BULK-BE-008 (backend) + BULK-ADM-001 through BULK-ADM-005. BULK-QA-001 is required before go-live. **Migration risks**: None — bulk update is purely additive; no existing data migration required.