Skip to content

Verificación post-migración

Checks que se corren después de cada fase para confirmar integridad.

1. Conteos por colección

Esperados vs. reales:

js
// En mongosh contra el Mongo destino (port 27019)
const expected = {
  companies: 1,
  users: 6,                    // ajustar al real de Trebol
  custom_field_definitions: 227,
  sales_pipelines: 27,
  contacts_new: 44918,
  opportunities: 51495,
  conversations: 44909
};

Object.entries(expected).forEach(([col, exp]) => {
  const got = db[col].countDocuments({});
  const ok = got === exp ? "OK" : "MISMATCH";
  print(`${col}: esperado=${exp} actual=${got} [${ok}]`);
});

Pequeñas diferencias (< 1%) son aceptables si hay deletes en GHL durante la corrida.

2. Sampling de campos críticos

js
// Contactos sin nombre
db.contacts_new.countDocuments({ $or: [{ name: "" }, { name: null }] })  // debe ser 0

// Contactos sin external_id (huérfanos del mapping)
db.contacts_new.countDocuments({ external_id: { $exists: false } })  // 0

// Oportunidades cuyo contacto no se migró
db.opportunities.aggregate([
  { $lookup: { from: "contacts_new", localField: "contact_id", foreignField: "_id", as: "c" } },
  { $match: { c: { $size: 0 } } },
  { $count: "huerfanas" }
])
// Idealmente 0 — si > 0 son contactos borrados en GHL pero con opp aún viva

3. Verificación de mapping de pipelines

js
// Cada opportunity debe tener pipeline name resuelto
db.opportunities.countDocuments({ pipeline: { $in: [null, ""] } })  // debe ser 0
db.opportunities.countDocuments({ stage: { $in: [null, ""] } })     // debe ser 0

4. Custom fields resueltos

js
// Spot-check: tomar 10 contactos al azar y ver que customFields tengan keys legibles (no IDs)
db.contacts_new.aggregate([
  { $sample: { size: 10 } },
  { $project: { name: 1, custom_fields_keys: { $objectToArray: "$custom_fields" } } }
])
// Las keys deben ser tipo "company_name", "date_of_birth", "T0W74DhZbxPxAcUgtqwE"
// Cualquier key con formato GHL-id (20 chars alfanuméricos) significa que faltó resolver

5. Mensajes vinculados

js
// Cada mensaje debe tener su conversación
db.messages.aggregate([
  { $lookup: { from: "conversations", localField: "conversation_id", foreignField: "_id", as: "c" } },
  { $match: { c: { $size: 0 } } },
  { $count: "msgs_huerfanos" }
])
// 0 ideal

6. DLQ (Dead Letter Queue)

bash
# Total de docs descartados
wc -l migrations/logs/dlq-*.jsonl

# Top razones de descarte
cat migrations/logs/dlq-*.jsonl | jq -r '.reason' | sort | uniq -c | sort -rn

Razones esperadas y aceptables:

  • duplicate_email — contactos con email repetido
  • missing_required_field — contactos sin firstName ni phone ni email

Razones que requieren investigación:

  • unknown_pipeline_id — un opportunity apunta a un pipeline no migrado
  • validation_error con stack trace

7. Sanity check end-to-end

Login como un usuario de Trebol en el frontend (o con curl al backend):

bash
TOKEN=$(curl -s -X POST http://localhost:3434/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin@trebol.com","password":"admin"}' | jq -r .token)

# Lista de contactos paginada
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:3434/api/v1/contacts?limit=10" | jq '.data | length'
# Debe regresar 10

# Detalle de un contacto migrado
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:3434/api/v1/contacts/by-external-id/8McTOURh47cMDDqn3QkN" | jq

8. Reporte de cierre

Una vez todos los checks pasen, generar el reporte:

bash
python3 scripts/migrate.py --report > logs/migration-report.txt

Contenido esperado:

  • Conteos finales por colección
  • Tiempo total de migración
  • Total de docs en DLQ por colección
  • Hash de verificación (sha256 de los conteos serializados)