Etapa 1: plan de ejecución, corte de avance del 16-jul y fuentes
- Plan-Etapa1.md: plan detallado por fases/bloques (B0-B9) con horas, decisiones de arquitectura y lo bloqueado hasta el 22-jul - Avance-Etapa1-2026-07-16.md + Excel de avance ordenado por fechas (con script generador y de ordenamiento) - WhatsApp 16-jul: invitación al Git recibida, procedimientos de clientes enviados por correo, Erika pide avance de Etapa 1 - Actualiza PENDIENTES y REGISTRO
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# Plan de ejecución — Etapa 1 · Plataforma base y sincronización con BIND
|
||||
|
||||
**Proyecto:** Plataforma de Automatización Financiera · Balam
|
||||
**Ventana:** 13–24 jul 2026 (semanas 2–3) · **Rango contractual:** 32–39 h · $19,200–$23,400 + IVA
|
||||
**Corte de referencia:** 16-jul-2026 · 8.00 h registradas → restan ~24–31 h
|
||||
**Repo de código:** `github.com/pedro-balam-itsm/BALAM` (clonado en `balam-plataforma/`)
|
||||
|
||||
> Documento interno de Johann. NO compartir con Balam ni commitear en el repo del cliente.
|
||||
> Fuentes: propuesta v1.2 (§Etapa 1, L142–157), ADR-001…006 (`Hallazgos-y-decisiones-Etapa0.md`),
|
||||
> `bind-api-sandbox/VALIDACION-API.md`, `Seguimiento-horas.md`, REGISTRO #40–#45.
|
||||
|
||||
## 1. Estado al 16-jul — entregables contractuales
|
||||
|
||||
Objetivo contractual: *"Plataforma funcional que ingiere clientes y facturas de BIND vía API, los normaliza, mantiene su trazabilidad y habilita la capa de escritura controlada."*
|
||||
|
||||
| # | Entregable (propuesta L148–155) | Estado |
|
||||
|---|---|---|
|
||||
| 1 | Backend con autenticación, roles (Finanzas, Dirección, Operaciones, Administración) y bitácora de auditoría universal | ❌ Solo existe la entidad `AuditLogEntry` |
|
||||
| 2 | Arquitectura multi-tenant (`tenant_id` + RLS), sin alta autoservicio | ❌ |
|
||||
| 3 | Cliente de la API de BIND (reintentos, OData, límites, errores) | ✅ **Terminado** — C#, 13 pruebas xUnit, salvaguardas del sandbox |
|
||||
| 4 | Capa de escritura controlada (cotización→factura con dry-run + confirmación + feature flag + bitácora) | ❌ Bloqueada hasta el 22-jul (solo backend; la UI es Etapa 2) |
|
||||
| 5 | Sincronización programada de clientes (normalización y deduplicación) | ❌ |
|
||||
| 6 | Sincronización programada de facturas y cotizaciones (estado normalizado, tipo de cambio, paginación) | ❌ |
|
||||
| 7 | Modelo central de facturas con estados (Emitida, Vigente, Vencida, Pagada, Cancelada) | ❌ |
|
||||
| 8 | Migraciones versionadas y datos de prueba | ❌ |
|
||||
|
||||
Avance parcial no contractual ya hecho: solución .NET 10 compilando (Api/Domain/Infrastructure/Integrations.Bind), `BalamDbContext` con `SyncCheckpoint`/`AuditLogEntry`, endpoints `/health` y `/api/bind/status`, frontend Angular 21 + Material con tema de marca, CLAUDE.md del repo con reglas duras y salvaguardas.
|
||||
|
||||
## 2. Calendario e hitos externos
|
||||
|
||||
| Fecha | Hito |
|
||||
|---|---|
|
||||
| jue 17-jul | Fin de la ventana planeada de backend base + multi-tenant |
|
||||
| **vie 18-jul** | **Demo semanal (30 min, Pedro + gerencia):** meta = login + roles + sync real de BIND en Postgres + endpoint de cartera |
|
||||
| sem 21-jul | Acceso Azure (Pedro + Noé) — no bloquea trabajo local |
|
||||
| **mié 22-jul** | **Sesión de definiciones Etapa 1** (Arturo + CEO): alta de cliente, fee, particularidades de envío, alcance Jira→BIND. Agenda en [Sesion-Etapa1-2026-07-22.md](Sesion-Etapa1-2026-07-22.md) |
|
||||
| jue 23-jul 7 pm | Validación del prototipo de Etapa 0 |
|
||||
| vie 24-jul | CI/CD + gestión de secretos · demo y cierre de etapa |
|
||||
|
||||
## 3. Plan de construcción — fases y bloques
|
||||
|
||||
Dependencias: B1 → (B2, B3); B3 → B4; B4 → B5; B1 → B6; B2+B1 → B7; todo → B8/B9. B6 y B7 son paralelizables.
|
||||
**Mínimo para la demo del 18-jul:** B0 + B1 + B2-núcleo + B3 + B4 (≈ 14–15 h desde el 16-jul).
|
||||
|
||||
### F1 (16-jul) — Fundación de datos
|
||||
|
||||
- **B0 · Preparación local (0.5–1 h):** `docker-compose.yml` con Postgres 17 (puerto 5433 para no chocar); cadena de conexión en `appsettings.Development.json`; `DesignTimeDbContextFactory`; referencia Infrastructure → Integrations.Bind. Docker 29.x ya instalado; `dotnet-ef` 10.0.4 global (invocarlo desde PowerShell — el PATH de git-bash no lo ve).
|
||||
- **B1 · Modelo de datos + Identity + migración inicial + seed (3.5–4.5 h):**
|
||||
- Entidades nuevas en `Balam.Domain/Entities/`: `Tenant`, `Client` (Id = GUID de BIND; `NormalizedRfc`, `NormalizedName`, `DuplicateOfClientId`, `SourceHash`, `DetailSyncedAtUtc`), `Invoice` (modelo central: `Uuid` null ⇒ prefactura, `OpenBalance` recalculado en sync, `CfdiPaymentTermRaw` PPD/PUE del detalle, `PaidDetectedAtUtc`, `LastKnownPlatformStatus`), `Quote`, `QuoteInvoiceLink` (ADR-003; incluye `JiraTicketKey` — ADR-006), `InvoiceStatusChange` (ADR-004), `WriteOperation` (nace aquí para no re-migrar).
|
||||
- Extender `SyncCheckpoint` y `AuditLogEntry` con `TenantId`; índice único `(TenantId, Resource)`.
|
||||
- `BalamDbContext` → `IdentityDbContext<BalamUser, IdentityRole<Guid>, Guid>` (`BalamUser` vive en Infrastructure; Domain queda puro).
|
||||
- Convenciones ANTES de la primera migración: fechas de BIND (sin zona) → `timestamp without time zone`; timestamps propios `*Utc` → `timestamptz`; montos `decimal(18,2)`, `ExchangeRate (18,6)`.
|
||||
- Migración `Inicial` + `DbSeeder` idempotente: tenant Balam (GUID fijo), 4 roles, 5 usuarios ficticios `*@balam.dev` (password vía `Seed:DefaultPassword`; jamás datos reales).
|
||||
|
||||
### F2 (17-jul) — Seguridad, bitácora y primer sync real
|
||||
|
||||
- **B2 · Auth + roles + bitácora (4–5 h):** ASP.NET Core Identity + JWT propio (`AddIdentityCore` + `AddJwtBearer`; NO `MapIdentityApi` — emite tokens opacos, el contrato dice JWT). `POST /api/auth/login`, `GET /api/auth/me`. Roles sin acentos como identificador (`Finanzas`, `Direccion`, `Operaciones`, `Administracion`). Policies nombradas (`PuedeVerCartera`, `PuedeDispararSync`, `PuedeVerBitacora`, `PuedeOperarEscritura`). `Jwt:SigningKey` en user-secrets/Key Vault con fail-fast (mismo patrón que `Bind:ApiKey`). **Sin refresh tokens en E1** (se difieren a Etapa 2 con la UI). Bitácora universal en dos piezas — NO interceptor de `SaveChanges` (inundaría con los upserts del sync): `AuditMiddleware` (peticiones mutantes + 401/403) e `IAuditLogger` explícito (logins, resumen de cada ciclo de sync, transiciones de estado, TODA operación de escritura). `GET /api/audit` (Direccion/Administracion).
|
||||
- **B3 · Sync de clientes (2.5–3.5 h):** la lista `Clients` NO tiene campo de fecha filtrable ⇒ full-scan barato de lista cada ciclo (~1–3 páginas con `PaginateAsync`) + comparación por `SourceHash` + **detalle solo para nuevos/cambiados** (ahí viven `CreditDays` y contactos). Normalización RFC/nombre (mayúsculas, sin acentos, sin sufijos societarios). Dedup MVP: marcar `DuplicateOfClientId` por `NormalizedRfc` exacto **excluyendo RFCs genéricos** (`XAXX010101000`/`XEXX010101000`); sin fuzzy matching. `POST /api/sync/run` (manual, para la demo) + `GET /api/sync/status`.
|
||||
|
||||
### F3 (18-jul am) — Cierre de demo
|
||||
|
||||
- **B4 · Sync de facturas/cotizaciones + estados + cartera (4.5–5.5 h):**
|
||||
- Facturas: delta `$filter=Date ge datetime'{cursor}'` + **re-barrido de la ventana de facturas abiertas** (los pagos caen en facturas viejas que el delta por fecha no atrapa) + detalle (1 GET c/u) SOLO nuevas/cambiadas con tope `Sync:MaxDetailPerCycle` (50). Nunca re-barrer todo el detalle (histórico > 1,000 filas).
|
||||
- Estados de plataforma como **función pura con pruebas** (`InvoiceStatusMapper`): Cancelada (`BindStatus=2`) → Pagada (`BindStatus=1` o saldo ≤ 0.01 con abonos) → Vencida (timbrada, saldo > 0, `ExpirationDate` < hoy MX) → Vigente (timbrada, en plazo) → Emitida (prefactura: `Uuid` null). ⚠️ Frontera Emitida/Vigente a confirmar el 22-jul — por eso es función pura, cambiarla cuesta minutos. Vencida/Vigente se computan en la consulta (dependen de "hoy"); lo persistido es `LastKnownPlatformStatus` solo para detectar transiciones.
|
||||
- Detección de pagos (ADR-004): transición → fila en `InvoiceStatusChanges` con **fecha de DETECCIÓN** (nunca presentarla como fecha valor) + `PaidDetectedAtUtc`.
|
||||
- Cotizaciones: delta por `CreationDate` con fallback a full-scan (filtro no validado contra producción); partidas `Items[]` NO se persisten en E1.
|
||||
- Moneda: catálogo `Currencies` (4 filas, 1 GET/ciclo) → `CurrencyCode` sin pedir detalle.
|
||||
- Endpoints: `GET /api/cartera` (agregados **por moneda — nunca sumar monedas entre sí**), `GET /api/invoices?status=&clientId=`, `GET /api/clients?search=` (marca duplicados), `GET /api/quotes`.
|
||||
- **El sync inicial del histórico se corre el jueves 17 en la noche — jamás en vivo en la demo** (en demo solo el incremental, <10 req).
|
||||
|
||||
### F4 (21-jul) — Robustez
|
||||
|
||||
- **B5 · Scheduler + backfill (2–3 h):** `BackgroundService` + `PeriodicTimer` cada 15 min (NO Quartz/Hangfire: un solo job, estado en checkpoints, sin dashboard). `SemaphoreSlim(1,1)` anti-solape; guarda de cuota vía `BindQuotaTracker` (si `Remaining < Sync:QuotaReserve` = 500, el ciclo se salta y se audita). Backfill gradual del detalle histórico por ciclo. Presupuesto validado: <10 req/ciclo ≈ 1,000 req/día = 5 % de la cuota. Nota Azure: requiere Always On + instancia única.
|
||||
- **B6 · RLS + tenancy (1.5–2.5 h):** RLS **real** de Postgres (compromiso contractual, no basta el filtro EF): migración con `ALTER TABLE ... ENABLE/FORCE ROW LEVEL SECURITY` + política `tenant_isolation` con `current_setting('app.tenant_id', true)::uuid` y `WITH CHECK`, por tabla de negocio; `TenantConnectionInterceptor` (`set_config` al abrir conexión); filtro global EF (`ITenantOwned` + `ICurrentTenantProvider` con tenant fijo del seed) como segunda capa. Fuera: CRUD de tenants, resolución por request, RLS sobre tablas Identity.
|
||||
|
||||
### F5 (22-jul, después de la sesión) — Escritura controlada
|
||||
|
||||
- **B7 · Capa de escritura controlada (3–4 h):** pipeline `Draft → DryRun → Confirm` persistido en `WriteOperations`, todo auditado. `BindWriteClient : BindClient` (hereda `RequestAsync` protected y sus salvaguardas). Flags `BindWrite:Enabled=false` y `BindWrite:AllowConvertToInvoice=false` por default. PPD default; PUE solo con `"confirmoPue": true` explícito (un PUE erróneo ya les costó un requerimiento del SAT). **CERO llamadas reales a BIND** — shapes de los POST provisionales (TODO hasta leer la doc del portal con Pedro); la conversión mock registra `QuoteInvoiceLink`. `Bind:Mode=Write` queda documentado como bloqueado hasta autorización expresa (ADR-001).
|
||||
|
||||
### F6 (23–24 jul) — Cierre
|
||||
|
||||
- **B8 · Suite de pruebas de flujos críticos (2.5–3.5 h):** nuevo proyecto `tests/Balam.Api.Tests` (xUnit + `WebApplicationFactory` + **SQLite in-memory**; NO EF InMemory — no valida FKs/únicos; NO Testcontainers — exige red). Cobertura contractual: matriz de roles 401/403/200, sync (delta/checkpoint/detección de pago), multimoneda (cartera no mezcla), salvaguardas de escritura (flag/modo/bitácora). Costo aceptado: RLS y `timestamptz` son Postgres-specific → verificación manual en runbook.
|
||||
- **B9 · Datos de prueba + runbook (1–2 h):** `DevDataSeeder` ficticio multimoneda/multi-estado (entregable 8 y respaldo de demo si BIND fallara); runbook: compose up → run (migra+siembra) → login → sync en vivo → cartera → bitácora → `/api/bind/status`.
|
||||
|
||||
## 4. Presupuesto de horas
|
||||
|
||||
| Bloque | Horas | ¿Recortable si aprieta? |
|
||||
|---|---|---|
|
||||
| B0 Preparación local | 0.5–1 | No |
|
||||
| B1 Modelo + Identity + migración + seed | 3.5–4.5 | No (fundacional) |
|
||||
| B2 Auth + roles + bitácora | 4–5 | `GET /api/audit` y middleware → post-demo (−1) |
|
||||
| B3 Sync clientes + dedup | 2.5–3.5 | Dedup solo RFC exacto (−0.5) |
|
||||
| B4 Sync facturas/cotizaciones + cartera | 4.5–5.5 | Equivalente MXN (−0.5); quotes full-scan simple (−0.5) |
|
||||
| B5 Scheduler + backfill | 2–3 | Backfill con tope fijo (−0.5) |
|
||||
| B6 RLS + tenancy | 1.5–2.5 | **No recortable a cero** (contractual) |
|
||||
| B7 Escritura controlada (dry-run) | 3–4 | Sin modelos BIND provisionales si el 22-jul cambia el alcance (−1) |
|
||||
| B8 Suite de pruebas | 2.5–3.5 | Solo casos redundantes |
|
||||
| B9 Datos de prueba + runbook + colchón | 1–2 | Seeder mínimo (−0.5) |
|
||||
| **Total** | **25–31** (nominal ~27.5) | Recortes identificados ≈ −5 h |
|
||||
|
||||
Primer recorte si el viernes se complica: bitácora consultable y sync de cotizaciones se posponen al lunes 21 — la demo exige login + roles + sync + cartera.
|
||||
|
||||
## 5. Decisiones de arquitectura (registro)
|
||||
|
||||
1. **Scheduler:** `BackgroundService` + `PeriodicTimer`, no Quartz/Hangfire — un solo job, estado ya persistido en `SyncCheckpoints` (ADR-002), cero esquema extra.
|
||||
2. **RLS en dos capas:** políticas reales de Postgres (compromiso "tenant_id + RLS") + filtro global EF como defensa; sin resolución de tenant por request (ADR-005: tenant único).
|
||||
3. **Identity + JWT sin refresh tokens** en E1; se agregan en Etapa 2 con la UI Angular.
|
||||
4. **Bitácora:** middleware HTTP + `IAuditLogger` explícito; NO interceptor EF (el primer sync insertaría > 1,000 entradas de ruido).
|
||||
5. **Pruebas con SQLite in-memory**; RLS/`timestamptz` quedan a verificación manual documentada (candidato a job de integración cuando exista CI el 24-jul).
|
||||
6. **Trampas resueltas por convención:** fechas Npgsql (BIND sin zona), "hoy" con `America/Mexico_City`, `partial class Program` para `WebApplicationFactory`, BD obligatoria con fail-fast.
|
||||
|
||||
## 6. Bloqueado y fuera de alcance
|
||||
|
||||
**Bloqueado hasta la sesión del 22-jul (no adelantar):** escritura real a BIND (ADR-001); reglas de alta de cliente, fee y particularidades de envío; alcance Jira→BIND (ADR-006 — solo se persiste el folio); semántica exacta Emitida/Vigente; mapeo `CFDIUse`→clave SAT.
|
||||
|
||||
**Fuera de la Etapa 1:** UI (Etapa 2); refresh tokens (E2); recordatorios a clientes (Anexo B); multiempresa (ADR-005); fecha valor de pagos/REP (ADR-004 — solo fecha de detección); aging 30/60/90 y tablero (Etapa 3); conciliación bancaria; CI/CD y Azure (actividades propias, 21–24 jul); envío de PDF (aunque `InvoicePdfAsync` ya existe).
|
||||
|
||||
## 7. Riesgos y pendientes de gestión
|
||||
|
||||
**Riesgos técnicos:** primera corrida del sync en vivo (mitigado: jueves noche); filtro `CreationDate` de Quotes sin validar (fallback full-scan desde el día uno); repo visible al cliente (solo datos ficticios, passwords de seed configurables, sin co-autoría en commits).
|
||||
|
||||
**Gestión que toca la etapa:**
|
||||
- Correo formal a Noé con las salvaguardas (borrador listo en [Borradores-seguimiento-tecnico.md](Borradores-seguimiento-tecnico.md)).
|
||||
- 5 preguntas técnicas a Pedro: pagos/REP, tabla `CFDIUse`→SAT, shape de `/{id}/xml`, webhooks vs polling, token del Power BI.
|
||||
- Revisar los **procedimientos de carga de facturas por cliente** que Erika envió por correo el 16-jul (insumo para particularidades de envío).
|
||||
- Borrar `bind_token_api.txt` del disco (el token ya vive en user-secrets).
|
||||
- Registrar horas diario en [Seguimiento-horas.csv](Seguimiento-horas.csv) y conciliar con Jira cuando Pedro lo habilite.
|
||||
- Preparar la sesión del 22-jul con [Sesion-Etapa1-2026-07-22.md](Sesion-Etapa1-2026-07-22.md).
|
||||
Reference in New Issue
Block a user