Saltar a contenido

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:

orden_efectivo = pre_orden if orden_final == 0 else orden_final

Esto permite ordenar por orden_efectivo:asc directamente en la query de Meilisearch.