Commit c9698bbc by Denys Tito

merge: integrar script de índices de Mongo

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
parents d5471808 eddfbd1e
......@@ -68,6 +68,10 @@ Balotarios: `internal/infrastructure/database/postgres.BalotarioQuery`, un docum
Comunicados: `internal/infrastructure/database/postgres.ComunicadoQuery`, un documento por comunicado (`intranet.comunicado` + `intranet.comunicado_destino`) con su regla de audiencia, no una fila por alumno: `alcance_tipo` (1 todos, 2 sedes, 3 aulas, 4 personalizado) y los arreglos `sede_ids`, `aula_ids` y `matricula_ids`. Se sincronizan también los comunicados vencidos o retirados, porque intranet-so-be filtra por `vigente_hasta` al leer y así retirar uno surte efecto al instante. Leído por `postgres.ComunicadoReader` hacia `entities.Comunicado`: colección `c_comunicados`, archivo `deleted_c_comunicados`, `IDField` = `comunicado_id`, `StateID` = 11 (el 10 lo usa la rama `pipelines-libreta`). Requiere índice único sobre `comunicado_id` en Mongo. Trismegisto dispara `POST /api/v1/sync/comunicados/upsert` con `{"id": <comunicado_id>}` al enviar o retirar. El DDL y las funciones están en `intranet-so-be/migrations/004_comunicados.sql`.
## Índices de Mongo
`scripts/mongo_indices.js` (para `mongosh`, ejecutar contra la base de cada ambiente) crea los índices que necesitan el synchronizer y intranet-so-be: únicos por `idField` de cada pipeline en `students`, `parents`, `users`, `professors`, `payment_plans`, `c_mensajes` y `c_comunicados`; `(matricula_id, mes)` en `c_attendance`, `(student_id, año, orden, id)` en `payment_plans`, `user_login` en `users`, los tres arreglos de audiencia de `c_comunicados` y los únicos de las colecciones de lecturas. Antes de crear el único de `payment_plans` elimina las copias exactas: en el ambiente de desarrollo había 96 documentos duplicados, producto de upserts concurrentes sin índice. Sin estos índices cada upsert y cada consulta recorre la colección completa (321 000 documentos en `c_attendance`, 228 000 en `payment_plans`).
## Mongo `_id` rule
**`_id` nunca es el id de negocio.** Todas las colecciones dejan que Mongo genere su propio ObjectID; el id de negocio (`student_id`, `payment_plan_id`, etc.) vive como campo normal del documento — el mismo declarado en `PipelineDef.IDField` (`internal/infrastructure/config/pipelines.go`). `repository.DocumentRepository` y `repository.DeletedRepository` reciben ese `idField` y filtran/listan/archivan por él, no por `_id`. Al archivar en la colección `deleted_*`, se descarta el `_id` original para que se genere uno nuevo. Cualquier pipeline nuevo debe seguir esta misma regla — no volver a usar `_id = <id de negocio>`. Requiere índice único sobre `idField` en cada colección Mongo (no lo impone el código).
......
const indices = [
{ coleccion: 'c_comunicados', claves: { comunicado_id: 1 }, opciones: { name: 'uq_comunicado_id', unique: true } },
{ coleccion: 'c_comunicados', claves: { sede_ids: 1 }, opciones: { name: 'ix_sede_ids' } },
{ coleccion: 'c_comunicados', claves: { aula_ids: 1 }, opciones: { name: 'ix_aula_ids' } },
{ coleccion: 'c_comunicados', claves: { matricula_ids: 1 }, opciones: { name: 'ix_matricula_ids' } },
{ coleccion: 'comunicado_lecturas', claves: { matricula_id: 1, comunicado_id: 1 }, opciones: { name: 'uq_matricula_comunicado', unique: true } },
{ coleccion: 'c_mensajes', claves: { matricula_id: 1 }, opciones: { name: 'uq_matricula_id', unique: true } },
{ coleccion: 'mensaje_lecturas', claves: { matricula_id: 1, mensaje_id: 1 }, opciones: { name: 'uq_matricula_mensaje', unique: true } },
{ coleccion: 'c_attendance', claves: { matricula_id: 1, mes: 1 }, opciones: { name: 'ix_matricula_mes' } },
{ coleccion: 'payment_plans', claves: { payment_plan_id: 1 }, opciones: { name: 'uq_payment_plan_id', unique: true } },
{ coleccion: 'payment_plans', claves: { student_id: 1, payment_plan_year: 1, payment_plan_order: 1, payment_plan_id: 1 }, opciones: { name: 'ix_student_orden' } },
{ coleccion: 'students', claves: { student_id: 1 }, opciones: { name: 'uq_student_id', unique: true } },
{ coleccion: 'parents', claves: { parent_id: 1 }, opciones: { name: 'uq_parent_id', unique: true } },
{ coleccion: 'users', claves: { user_id: 1 }, opciones: { name: 'uq_user_id', unique: true } },
{ coleccion: 'users', claves: { user_login: 1 }, opciones: { name: 'ix_user_login' } },
{ coleccion: 'professors', claves: { professor_id: 1 }, opciones: { name: 'uq_professor_id', unique: true } },
];
function eliminarCopiasExactasDePaymentPlans() {
const coleccion = db.getCollection('payment_plans');
const grupos = coleccion.aggregate([
{ $sort: { _id: 1 } },
{ $group: { _id: '$payment_plan_id', ids: { $push: '$_id' }, hashes: { $addToSet: '$row_hash' } } },
{ $match: { $expr: { $gt: [{ $size: '$ids' }, 1] } } },
], { allowDiskUse: true }).toArray();
const sobrantes = [];
grupos.forEach(grupo => {
if (grupo.hashes.length !== 1) {
print(`payment_plan_id ${grupo._id}: las copias difieren, revisar a mano`);
return;
}
grupo.ids.slice(1).forEach(id => sobrantes.push(id));
});
if (sobrantes.length > 0) {
const resultado = coleccion.deleteMany({ _id: { $in: sobrantes } });
print(`payment_plans: ${resultado.deletedCount} copias exactas eliminadas`);
}
}
eliminarCopiasExactasDePaymentPlans();
indices.forEach(({ coleccion, claves, opciones }) => {
const nombre = db.getCollection(coleccion).createIndex(claves, opciones);
print(`${coleccion}: ${nombre}`);
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment