Tres defectos que solo aparecieron al probar con los dos sistemas cableados. Ninguno lo
habrian encontrado las pruebas: usan SQLite con su propio registro de modelos y no
ejercitan el proceso del worker.
1. REGISTRO DE MODELOS EN EL WORKER. Celery no carga la app: importa el modulo de la
tarea y nada mas. SQLAlchemy resuelve las ForeignKey por nombre de tabla contra su
registro global, asi que sin la clase del otro extremo importada la configuracion de
mappers moria con "Foreign key associated with column 'cases.account_id' could not
find table 'crm.accounts'" y la tarea con PendingRollbackError.
El sintoma era cruel: la fila del outbox se quedaba en pending con attempts=0 y SIN
last_error --el fallo ocurre antes de poder registrarlo--, asi que el carril se veia
encolando bien y no entregaba nunca. En la app web no pasa porque main.py monta todos
los routers. Se importan los cuatro modelos del juego minimo verificado con
configure_mappers() en un proceso limpio.
2. LA MIGRACION NO RELLENABA LAS FILAS PREVIAS. Los expedientes creados antes del carril
quedaban con efc_storage_token NULL; el barrido de reconciliacion los encolaba, EFC los
rechazaba con {'storage_token': ['This field may not be null.']} y agotaban sus 8
intentos hasta failed. Ruido permanente por un dato derivable. La migracion ahora
rellena 'CRM-'||company_id||'-'||reference, con la misma condicion de longitud que la
guarda de storage_token: lo que no cabe en los 25 de pedimento_app se queda NULL a
proposito, porque un token recortado apuntaria a la carpeta de otro expediente.
3. SIN GUARDA DE FOLIO NULO. crm.cases.reference es nullable, y ni el encolado ni el
barrido de huecos lo comprobaban. Ahora los dos saltan lo que no tiene folio o token,
y el encolado lo avisa en WARNING: es una omision silenciosa --el expediente vive en el
CRM y sus documentos no llegaran a EFC-- y merece dejar rastro.
Verificado de punta a punta. Los TRES expedientes del CRM estan en EFC, leido desde EFC:
EXP2026-08-001 -> CRM-2-EXP2026-08-001 provisional
EXP2026-08-002 -> CRM-2-EXP2026-08-002 provisional
EXP2026-08-003 -> CRM-2-EXP2026-08-003 provisional
y los tres en LINKED con su outbox en sent. El 001 nacio antes del enganche y se recupero
por el camino del relleno + reintento, que es el que usaria una persona desde el tablero.
Ref: T2026-08-046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rebase del lado emisor de T2026-08-046 sobre esta rama. La entrega anterior partia
de feature/crm-cumplimiento-pdf (16-jul), 40 commits atras, y por eso construyo un
expediente PARALELO -- crm.expedientes con su propio generador de folio y su propia
migracion -- que duplicaba el que ya existe aqui. Dos expedientes y dos secuencias
peleando por el mismo namespace EXP no se fusionan; se tira el nuestro.
La estructura del expediente es de esta rama y no se toca: crm.cases es el
expediente, su folio vive en `reference` y el consecutivo lo reserva
crm/common/folios.py con bloqueo de fila. Nuestro aporte es SOLO la conexion:
- crm.cases gana seis columnas efc_* (espejo de EFC, nunca el handle) y nada mas;
- crm.efc_sync_outbox y crm.efc_file_outbox, el outbox transaccional, con
expediente_ref -> crm.cases.id;
- core/efc_client.py y crm/expediente_gateway/ (outbox, reintentos, barridos),
clonados del gateway Anexo22 -> EFC que ya corre en produccion;
- las ocho variables EFC_* en config. EFC_API_URL vacia = carril apagado.
Verificado contra la base real: next_folio(...,'EXP',None,with_direction=False)
devuelve EXP2026-08-001, identico al formato que el contrato con EFC exige, y
storage_token da CRM-{company}-{folio} de 22 caracteres sobre los 25 de
pedimento_app.
Se corrige un error del docstring de storage_token: decia que cabian companies de
7 digitos y son 6 (4+7+1+14 = 26 > 25). Ahora valida y falla ruidosamente en vez de
entregar un token recortado, que apuntaria a la carpeta de otro expediente y
mezclaria documentos en silencio.
El revision id de la migracion tirada (e6f7a8b9c0d1) chocaba con crm_catalog_items
de esta rama: dos migraciones distintas con el mismo id habrian roto alembic al
fusionar. La nueva es c5d6e7f8a9b0, aditiva sobre d4e5f6a7b8c9.
PENDIENTE: falta el pegamento que invocaba el carril desde los flujos de la app
(alta del provisional al mintear el folio, subida de documento -> outbox, rutas en
el router y UI). Por eso test_efc_outbox, test_gateway_rutas y tres casos de
test_contrato_efc todavia no colectan. El carril no esta cableado al router, asi
que la app funciona igual: backend y frontend responden 200.
Ref: T2026-08-046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>