Flujo de lotes
Describe cómo un cambio en MySQL termina reflejado en Meilisearch.
Diagrama completo
sequenceDiagram
actor Usuario
participant MySQL
participant Trigger
participant Queue as meilisearch_sync_queue
participant Worker as sync_worker.py
participant Meili as Meilisearch
Usuario->>MySQL: UPDATE HAC_CERTIFICADO SET precio_base=1500
MySQL->>Trigger: AFTER UPDATE
Trigger->>Queue: INSERT IGNORE (certificado_id=58170, is_delete=0)
Note over Queue: Si ya existía, no hace nada (PK)
Usuario->>MySQL: UPDATE ATRIBUTOS_VALORES SET valor='Angus'
MySQL->>Trigger: AFTER UPDATE (tablename='HAC_CERTIFICADOS')
Trigger->>Queue: INSERT IGNORE (certificado_id=58170, is_delete=0)
Note over Queue: Sigue siendo 1 solo registro
loop cada 10 segundos
Worker->>Queue: SELECT certificado_id, is_delete LIMIT 200
Queue-->>Worker: [{certificado_id: 58170, is_delete: 0}]
Worker->>MySQL: Query JOIN 12 tablas WHERE id_certificado IN (58170)
MySQL-->>Worker: Datos completos del lote
Worker->>Worker: limpiar_certificado() → documento Meilisearch
Worker->>Meili: POST /indexes/certificados/documents
Meili-->>Worker: {taskUid: 42}
Worker->>Queue: DELETE WHERE certificado_id IN (58170)
Worker->>Queue: COMMIT
end
Flujo de DELETE
Cuando se elimina un lote de MySQL:
sequenceDiagram
participant MySQL
participant Trigger
participant Queue as meilisearch_sync_queue
participant Worker as sync_worker.py
participant Meili as Meilisearch
MySQL->>Trigger: AFTER DELETE ON HAC_CERTIFICADO
Trigger->>Queue: INSERT ... ON DUPLICATE KEY UPDATE is_delete=1
Note over Queue: is_delete=1 aunque ya estuviera encolado como upsert
Worker->>Queue: SELECT → [{certificado_id: 58170, is_delete: 1}]
Worker->>Meili: POST /indexes/certificados/documents/delete-batch [58170]
Worker->>Queue: DELETE WHERE certificado_id IN (58170)
Deduplicación
El punto clave del diseño: si un lote se modifica múltiples veces antes de que el worker corra, solo se indexa una vez con el estado final.
flowchart LR
A[UPDATE precio] -->|INSERT IGNORE 58170| Q
B[UPDATE atributo raza] -->|INSERT IGNORE 58170 — ignorado, ya existe| Q
C[UPDATE establecimiento] -->|INSERT IGNORE 58170 — ignorado, ya existe| Q
Q[(cola: 1 fila\ncertificado_id=58170)] --> W[Worker]
W --> M[1 sola indexación\ncon estado final]
Transformación del documento
La función limpiar_certificado() convierte el resultado del JOIN en un documento Meilisearch:
# Entrada: dict con columnas del JOIN
{
"id_certificado": 58170,
"nombre_provincia": "SF", # abreviatura
"latlong": "-33.966702,-60.583302",
"orden_final": 0,
"pre_orden": 5,
"replace_lote": '"[raza]":"Angus","[peso]":"320"',
...
}
# Salida: documento para Meilisearch
{
"id": 58170,
"nombre_provincia": "Santa Fe", # expandida con mapa PROVINCIAS
"_geo": {"lat": -33.966702, "lng": -60.583302},
"orden_efectivo": 5, # pre_orden porque orden_final == 0
"attr_raza": "Angus", # campos dinámicos aplanados
"attr_peso": "320",
...
}
Casos especiales
Establecimiento con coordenadas en 0,0
Si latlong = '0.000000,0.000000' o está vacío, la query usa las coordenadas de la localidad (GEN_LOCALIDADES.latitud_localidad, longitud_localidad) como fallback. El campo _geo en Meilisearch solo se setea si las coordenadas son válidas.
Vendor oculto
Si HAC_REMATES.oculta_vendedor = 1, el campo nombre_cuenta se sube como null al índice para no revelar el vendedor.
Localidad/provincia oculta
Similar: si oculta_localidad = 1 o oculta_provincia = 1, los campos correspondientes van como null.
orden_efectivo
Meilisearch no soporta expresiones en el sort (CASE WHEN). Por eso se calcula en Python y se sube como campo pre-calculado:
Esto permite ordenar por orden_efectivo:asc directamente en la query de Meilisearch.