Tarea 67 — Deduplicación de Entidades, Solicitud de Asociación y Código Único — PLAN v2

Proyecto: LPDI SGR (eco.lpdi.co) · Fecha: 23 de julio de 2026 · Estado: PLAN v2 — incorpora tus 10 respuestas; pendiente de tu aprobación antes de ejecutar (tu punto 10)


0. Qué cambió respecto al v1 (resumen)

# Tu respuesta Cómo quedó en el v2
1 Correos enmascarados; rótulo "Admin en el sistema" Tarjeta con j***@acme.com y rótulo "Admin en el sistema" (nunca "Admin" a secas). Nombre completo visible. §3.2
2 "Crear nueva" siempre permitido, con auditoría; mismo usuario puede tener el mismo nombre en países distintos Rediseñado el índice único: ahora incluye el país y se ancla al estado real de formalización. Además encontramos que el índice actual está muerto (no protege nada) — ver §1. §3.4
3 Código solo al formalizar + regla transversal "nada en borrador recibe código" Regla documentada para DESIGN.md. Verificado en eventos: el código formateado de eventos SÍ cumple (solo al enviar); el codigo_unico SERIAL de eventos NO cumple (se asigna al guardar el borrador) — señalado como deuda. §5.6
4 Código: prefijo telefónico, año a confirmar, tipo granular extensible, consecutivo a 5 dígitos Estructura reescrita: TTT-PPP-AA-NNNNN con taxonomía de tipos en tabla de catálogo (43 códigos seed, agregar uno = 1 fila). Ejemplos nuevos. §5
5 Aprobador asigna permisos + cargo, gestionado en PS/PI Confirmado: PS/PI es el lugar correcto y consistente con cómo se gestiona hoy el equipo (verificado en el código, ver §4.2). Flujo de aprobación ajustado. §4
6 10 días + recordatorio día 5 + correos a ambas partes en creación y resolución Matriz de correos actualizada. §4.4
7 Barrido de duplicados existentes Incorporado como informe solo-lectura, y además es prerrequisito técnico del nuevo índice único. §6
8 Código visible en PS/PI Incorporado. §5.5
9 Columna de código en las dos consultas Incorporado con las reglas del DS de tablas anchas. §7
10 Revisas el v2 antes de ejecutar Este documento. Nada se ejecuta sin tu OK.

Hallazgo nuevo del v2 (importante): al revisar el índice único para incorporar tu punto 2, encontramos que la protección actual contra duplicados del mismo usuario dejó de operar cuando se introdujo el modelo de formalización el 12 de julio. El detalle en §1. La buena noticia: el rediseño que pediste por países lo corrige de paso.


1. Diagnóstico actualizado (verificado en el código)

Lo del v1 sigue vigente: nada impide que dos usuarios distintos registren la misma empresa; el aviso del formulario es ignorable; el endpoint /api/check-company-name solo busca coincidencia exacta insensible a mayúsculas (pero sensible a estructura: no cruza startups contra ICGs ni usa el nombre legal).

Hallazgo nuevo — el índice único actual está muerto:

Esto convierte el rediseño del constraint (tu punto 2) de un "ajuste" a una reparación necesaria, y explica por qué el barrido de duplicados (tu punto 7) pasa a ser también un prerrequisito técnico: antes de crear el índice nuevo hay que conocer y resolver las colisiones ya existentes, o la creación del índice falla.

Otros datos verificados que condicionan el diseño:


2. Evaluación (sin cambios de fondo)

La propuesta sigue siendo correcta y ejecutable: convertir el aviso pasivo en una decisión consciente (crear nueva vs. solicitar asociación), con verificación humana de pertenencia por parte de quien administra la entidad existente. Tus 10 respuestas cierran todas las decisiones abiertas del v1 salvo una (el año del código, §9.1), que dejamos como recomendación explícita para tu confirmación.


3. Diseño de deduplicación (actualizado)

3.1 Detección de coincidencias

Igual que el v1: nuevo endpoint que busca contra startups e ICGs en dos niveles — (1) coincidencia exacta normalizada (sin tildes ni mayúsculas, reutilizando company_name_norm/organization_name_norm); (2) coincidencia aproximada por similitud (pg_trgm) que captura "Acme SAS" vs "Acme S.A.S." y errores de tipeo. Compara nombre comercial, nombre legal y nombre de ICG. Solo entidades formalizadas y no eliminadas.

3.2 La tarjeta de coincidencia (con tus ajustes del punto 1)

Encontramos entidades parecidas a "Acme"
┌──────────────────────────────────────────────────┐
│ ACME S.A.S. (Startup) · Colombia                 │
│ Código: STA-057-26-00042                         │
│ Nombre legal: Acme Tecnología S.A.S.             │
│ Admin en el sistema: Juan Pérez (j***@acme.com)  │
│ CEO: María Gómez (m***@acme.com)                 │
│ Web: acme.com   ·   Coincidencia exacta          │
│ [Es mi empresa, solicitar asociación]            │
└──────────────────────────────────────────────────┘
[No es ninguna, crear entidad nueva]

3.3 "Crear nueva de todas formas"

Siempre permitido. Cuando el usuario ignora una coincidencia exacta, se deja rastro en action_audit_log con un action_type nuevo (p. ej. entity_created_over_exact_match), incluyendo en metadata el/los ids de las entidades que coincidían. Nota técnica obligatoria (gotcha conocido de este repo): agregar un action_type implica tocar el tipo TS y el CHECK constraint de action_audit_log (la última versión del CHECK está en la migración 128; logAction traga el error si no se hace → falla silenciosa).

3.4 El índice único nuevo (tu punto 2 — mismo usuario, países distintos)

Reemplaza al índice muerto. Diseño:

-- startups (gemelo equivalente en icgs con organization_name)
DROP INDEX IF EXISTS startups_registered_by_company_unique;
CREATE UNIQUE INDEX startups_registered_by_company_country_unique
  ON startups (registered_by_id, immutable_unaccent(company_name), immutable_unaccent(country))
  WHERE company_name != ''
    AND formalized_at IS NOT NULL
    AND deleted_at IS NULL;

Qué logra, punto por punto:

Caso Resultado
Mismo usuario, mismo nombre, mismo país Bloqueado (la protección real que pediste conservar)
Mismo usuario, mismo nombre, países distintos Permitido (tu regla nueva: "Acme Colombia" y "Acme Perú" del mismo usuario conviven)
Mismo usuario, N borradores con el mismo nombre Permitido (los borradores no cuentan — formalized_at IS NULL)
Usuarios distintos, mismo nombre Permitido a nivel de base (como hoy) — lo gobierna el flujo de coincidencias + asociación, no un bloqueo duro
"Ácme" vs "acme" Cuentan como el mismo nombre (normalización con immutable_unaccent, ya existente)

Prerrequisitos en orden (por eso el barrido va primero): 1. Data-fix de países legacy: convertir los valores ISO2 ("CO") a nombre ("Colombia") para que el país del constraint compare manzanas con manzanas. Acotado, con snapshot previo (Supabase sin PITR) y reversible. 2. Pre-chequeo de colisiones: query que detecta filas que violarían el índice nuevo. Si hay colisiones, van al informe del barrido (§6) y las resuelve el equipo LPDI caso por caso ANTES de crear el índice (si no, el CREATE UNIQUE INDEX falla). 3. Crear el índice nuevo y eliminar el viejo. Todo idempotente y reversible.


4. Flujo de solicitud de asociación (actualizado)

4.1 Lo que se mantiene del v1

Nueva tabla entity_join_requests con token. Quién aprueba = exactamente quien ya puede editar la entidad: en startup, el registrador, el CEO o un miembro verificado con edición; en ICG, solo el registrador (regla dura tuya: los contactos ICG nunca editan). El solicitante debe estar verificado (Opción B). Vigencia 10 días, recordatorio al día 5 — replicando el motor temporal ya probado de las transferencias (RPC tick + cron + correos, patrón de entity_transfer_tick).

4.2 Al aprobar: permisos + cargo, en el PS/PI (tu punto 5)

Confirmado: sí, el panel de la entidad es el lugar correcto, y es consistente con cómo se gestiona hoy el equipo. Verificado en el código:

Flujo ajustado: las solicitudes pendientes aparecen en el PS (startup) o PI (ICG) de la entidad. Al aprobar, en esa misma pantalla el aprobador asigna de una vez:

4.3 Ciclo de vida

pending → (approved | rejected | expired a los 10 días). Recordatorio al aprobador el día 5. Todo transiciona vía RPC atómica (sin dobles aprobaciones) y deja rastro en action_audit_log (action_types nuevos: join_request_created, join_request_approved, join_request_rejected, join_request_expired — mismo gotcha del CHECK, §3.3).

4.4 Matriz de correos (actualizada con tu punto 6)

Evento Al solicitante Al aprobador / aprobadores
Solicitud creada ✅ constancia "tu solicitud fue enviada a los administradores de X" ✅ con la tarjeta del solicitante y el enlace de decisión
Día 5 sin respuesta ✅ recordatorio
Aprobada ✅ "ya haces parte de X" (con su cargo) ✅ confirmación de lo que aprobó
Rechazada ✅ notificación (sin exponer quién rechazó) ✅ constancia
Expirada (día 10) ✅ "venció sin respuesta; puedes crear la entidad o volver a intentar" ✅ constancia informativa

(La fila "expirada → ambas partes" es una extensión menor de tu regla, por simetría; puedes recortarla — §9.4.)

Los renders de correo siguen el patrón del repo (script de render separado, mismo layout de los correos de entidad existentes).


5. Código único de entidad (reescrito con tus 4 ajustes del punto 4)

5.1 Estructura

TTT-PPP-AA-NNNNN — ejemplos: STA-057-26-00042, VCF-051-26-00007.

Segmento Qué es Detalle
TTT Tipo granular (3 letras) Subcategoría de segundo nivel, desde tabla de catálogo (§5.2). Reemplaza al ST/IC plano del v1
PPP Prefijo telefónico del país (3 dígitos) El MISMO criterio del código de eventos, verificado en el código: se replica getPhonePrefix3 — Colombia → 057, Perú → 051, México → 052; sin país → 000. (Eventos usa el prefijo telefónico rellenado a 3 dígitos, no el ISO numérico)
AA Año (2 dígitos) Recomendación: año de registro en el sistema (fecha de formalización) — pendiente de tu confirmación, §9.1
NNNNN Consecutivo a 5 dígitos Contador atómico en base de datos por año, réplica exacta del patrón de eventos (evento_codigo_counters + RPC next_evento_consecutivo): tabla entidad_codigo_counters + RPC propia. Sin colisiones por concurrencia

El consecutivo se propone por año, compartido entre startups e ICGs (igual que eventos, que llevan un solo contador por año): mantiene los números cortos y el código ya distingue el tipo en TTT. Si prefieres contadores separados por tipo, es un cambio de una línea — §9.2.

5.2 Taxonomía de códigos de tipo (extensible — tu punto 4c)

Nueva tabla de catálogo catalogo_codigo_tipo_entidad: mapea cada subcategoría de segundo nivel a su código corto de 3 letras. Agregar una categoría o subcategoría nueva = insertar 1 fila (no tocar código de aplicación).

CREATE TABLE catalogo_codigo_tipo_entidad (
  id           SERIAL PRIMARY KEY,
  rol_codigo   VARCHAR(100) NOT NULL UNIQUE,  -- referencia al codigo de catalogo_rol_ecosistema
  codigo_corto CHAR(3)      NOT NULL UNIQUE,  -- el TTT del código de entidad
  activo       BOOLEAN      NOT NULL DEFAULT true,
  created_at   TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

Cómo conecta con lo que ya existe (verificado en el código):

Seed propuesto (los 43 códigos — puedes ajustar cualquier letra, §9.3):

Categoría Subcategoría (2.º nivel) Código
Inversionista Individual Ángel Inversionista ANG
Inversionista Individual Limited Partner LIP
Inversionista Individual General Partner GEP
Inversionista Individual Lead Investor LIN
Inversionista Institucional Family Office FAM
Inversionista Institucional Fondo de Inversión FDI
Inversionista Institucional Banca de Inversión BAN
Inversionista Institucional Venture Capital Fund – VC VCF
Inversionista Institucional Corporate Venture Capital – CVC CVC
Inversionista Institucional Government Venture Capital – GVC GVC
Gestor especializado Incubadora INC
Gestor especializado Aceleradora ACE
Gestor especializado Cámara de Comercio CAM
Gestor especializado Caja de Compensación CDC
Gestor especializado Company Builder CBU
Gestor especializado Startup Studio SST
Corporativo Empresa Privada EMP
Corporativo Empresa Mixta EMX
Consultora Gran consultora GCO
Consultora Consultora boutique CBO
Consultora Consultor independiente COI
Startup Startup STA
Startup Scaleup SCA
Startup PyME PYM
Gobierno Entidad Pública Municipal GPM
Gobierno Entidad Pública Regional GPR
Gobierno Entidad Pública Nacional GPN
Gobierno Organismo Multilateral OMU
Academia e Innovación Universidad UNI
Academia e Innovación Centro de Innovación CEI
Academia e Innovación Centro de Investigación CEV
Academia e Innovación Otros centros de formación OCF
Hub y Redes Hub de Innovación HUB
Hub y Redes Red de Innovación RDI
Hub y Redes Red de Emprendimiento RDE
Hub y Redes Clúster Tecnológico CLU
Hub y Redes Comunidad COM
Otros Asociación ASO
Otros Gremio GRE
Otros Fundación FUN
Otros Medio de comunicación MED
Otros ONG ONG
Otros Otro OTR

5.3 Ejemplos completos

Entidad Código Lectura
Startup en Colombia, formalizada en 2026, consecutivo 41 STA-057-26-00041 Startup · Colombia (+57) · 2026 · #41
PyME en México, 2026, consecutivo 87 PYM-052-26-00087 PyME · México (+52) · 2026 · #87
ICG fondo VC en Perú, 2026, consecutivo 12 VCF-051-26-00012 Venture Capital Fund · Perú (+51) · 2026 · #12
ICG Cámara de Comercio en Colombia, 2026, consecutivo 104 CAM-057-26-00104 Cámara de Comercio · Colombia (+57) · 2026 · #104

Nota: el código es inmutable desde su asignación. Si la entidad luego cambia de tipo o de país, el código no se regenera (es un identificador, no un atributo vivo) — igual criterio que eventos.

5.4 Cuándo se asigna, backfill

5.5 Dónde se ve el código

5.6 Regla transversal nueva del sistema (tu punto 3) — para DESIGN.md

Nada que esté en BORRADOR recibe código. Los códigos únicos del sistema (entidades, eventos, convocatorias, noticias y cualquier módulo futuro) se asignan únicamente al formalizar/enviar, nunca al crear o autoguardar un borrador. Un borrador abandonado no debe quemar números ni dejar identificadores huérfanos.

Verificación de cómo se comportan hoy los eventos (lo pediste explícito):

El código de entidades nace ya conforme a la regla (solo en formalizeEntity).


6. Barrido de duplicados existentes (tu punto 7)

Informe solo lectura sobre la base actual (sin tocar ni un dato), con el mismo matching del §3.1:


7. Columna "Código" en las consultas (tu punto 9)

Verificado el estado actual de ambas páginas:

Se agrega Código inmediatamente después del nombre (Compañía/Organización), ordenable, con para entidades sin código (borradores). Cumpliendo las reglas del DS de tablas anchas (DESIGN.md): mismos valores fijos de toda tabla ancha del dashboard, columna Acciones sticky a la derecha intacta (con su sombra de separación y su skipClass en la medición de anchos), scroll horizontal en el contenedor — prohibido ajustar una tabla por su cuenta.


8. Plan de implementación (13 tareas, 5 olas)

Todas las migraciones: aditivas, idempotentes y reversibles, con snapshot previo (Supabase sin PITR, prohibido DELETE sin snapshot). Numeración de migraciones desde 130_. Zonas de alto riesgo (migraciones + data-fix + RLS) con manos Opus y verificación con evidencia (psql real, no reporte en prosa); las tareas mecánicas con Sonnet.

Waves:

Wave Tasks Depende de Parallelizable Riesgo
0 1, 2 Sí (solo lectura + doc) Bajo
1 3, 4, 5 Wave 0 (T3 requiere colisiones resueltas del T1) ALTO — Opus (migraciones)
2 6, 7, 8 Wave 1 Medio — Opus
3 9, 10, 11, 12 Wave 2 Bajo-medio
4 13 Waves 1–3 No (integración) Medio — Opus

Task 1: Barrido de duplicados + verificación del estado real (Wave 0) — manos Opus

Files: - Create: script de informe solo-lectura (queries de matching exacto/aproximado + colisiones duras) - Create: informe entregable para el equipo LPDI

Done when: - [ ] Informe generado con grupos de duplicados (exactos y aproximados) y sección "colisiones duras" del índice nuevo - [ ] Verificado con psql el estado real: pg_indexes de los índices únicos actuales + conteo de filas con status != 'DRAFT' (confirmar el hallazgo del índice muerto) - [ ] Cero escrituras a la base: el script solo hace SELECT (revisión del código del script)

Task 2: Regla transversal en DESIGN.md + deuda de eventos (Wave 0) — manos Sonnet

Files: - Edit: DESIGN.md (regla "nada en borrador recibe código" + deuda codigo_unico SERIAL de eventos)

Done when: - [ ] DESIGN.md contiene la regla transversal con la redacción del §5.6 y la deuda de eventos documentada - [ ] No se tocó ningún archivo de código (solo documentación)

Task 3: Migración — índice único nuevo + data-fix países legacy (Wave 1)ALTO RIESGO, manos Opus

Files: - Create: supabase/migrations/130_dedup_unique_index_country.sql (+ rollback)

Done when: - [ ] Snapshot previo tomado y verificado (conteo de filas coincide) - [ ] Data-fix ISO2→nombre aplicado solo a los valores legacy detectados; SELECT de control devuelve 0 países en formato ISO2 - [ ] Índice nuevo creado y viejo eliminado; probado con psql: inserción duplicada (mismo usuario+nombre+país formalizada) falla con 23505, y mismo nombre en país distinto pasa - [ ] Idempotencia probada: la migración corre dos veces sin error - [ ] Rollback probado en local: restaura índice viejo y no pierde datos

Task 4: Migración — tabla entity_join_requests + RPC tick (Wave 1)ALTO RIESGO, manos Opus

Files: - Create: supabase/migrations/131_entity_join_requests.sql (+ rollback)

Done when: - [ ] Tabla con estados, token, expiración a 10 días y RLS deny-by-default (acceso vía service role, patrón icg_contacts) - [ ] RPC de transición atómica probada con psql: dos aprobaciones concurrentes → solo una gana - [ ] RPC tick (expiración + recordatorio día 5) probada con filas de prueba retro-fechadas y limpiadas después - [ ] Idempotencia y rollback probados

Task 5: Migración — catálogo de códigos + contador + columnas de código + backfill + audit CHECK (Wave 1)ALTO RIESGO, manos Opus

Files: - Create: supabase/migrations/132_entity_code.sql (+ rollback)

Done when: - [ ] catalogo_codigo_tipo_entidad creada y sembrada con las 43 filas del §5.2; codigo_corto UNIQUE - [ ] Columna entity_code (UNIQUE, nullable) en startups e icgs + tabla entidad_codigo_counters + RPC consecutivo atómica (réplica del patrón de eventos) - [ ] Backfill completo: 100% de entidades formalizadas con código, orden por formalized_at asc verificado con psql (muestra de 10 antiguas/nuevas); borradores en NULL - [ ] CHECK de action_audit_log reescrito con los 5 action_types nuevos; INSERT de prueba de cada tipo pasa (y se limpia) - [ ] Idempotencia y rollback probados

Task 6: Backend — endpoint de coincidencias (Wave 2) — manos Opus

Files: - Create: src/routes/api/entidades/similares/+server.ts (nombre final según convención del repo) - Create: tests del enmascarado y del matching

Done when: - [ ] Responde exactos + aproximados cruzando startups/ICGs con los campos de la tarjeta §3.2 - [ ] Correos enmascarados EN EL SERVIDOR (test: la respuesta JSON nunca contiene un correo completo) y rótulo "Admin en el sistema" - [ ] Solo entidades formalizadas y no eliminadas; rate-limit/min-length para no ser un oráculo de scraping - [ ] npm run test y npm run lint sin errores nuevos

Task 7: Backend — lib de solicitudes + correos + cron (Wave 2) — manos Opus

Files: - Create: src/lib/server/join-requests.ts + renders de correo (script de render separado, patrón del repo) - Create: src/routes/api/cron/join-request-tick/+server.ts (patrón entity-transfer-tick, protegido por CRON_SECRET) - Edit: vercel.json (cron nuevo — recordar: los crons de Vercel disparan por GET)

Done when: - [ ] Los 5 correos de la matriz §4.4 se envían a las partes correctas (test por evento: creada→2, aprobada→2, rechazada→2, expirada→2, recordatorio→1) - [ ] Aprobación crea la fila en startup_founders (con role + can_edit_form elegidos) o icg_contacts (con position), verificada - [ ] Solicitante no verificado (Opción B) recibe 403; auditoría registrada en cada transición - [ ] Tests pasan; render de correos generado y revisado visualmente

Task 8: Backend — asignación de código al formalizar (Wave 2) — manos Opus

Files: - Edit: src/lib/server/entities.ts (formalizeEntity) + helper de composición del código - Create: tests del compositor (TTT/PPP/AA/NNNNN, fallbacks OTR/000)

Done when: - [ ] Toda formalización (público y dashboard) asigna código exactamente una vez; idempotente (segunda llamada no regenera) - [ ] Fallbacks probados: ICG sin icg_role → OTR; sin país → 000; país legacy ISO2 → resuelve el prefijo correcto - [ ] PPP replica el criterio de eventos (CO→057, PE→051) con test que lo compara contra getPhonePrefix3 - [ ] Tests y lint sin errores nuevos

Task 9: UI — modal de coincidencias en los 4 formularios (Wave 3) — manos Opus

Files: - Create: componente de tarjetas de coincidencia (consumiendo el DS) - Edit: FSS/FSD/FIS/FID (los 4 shells de formulario)

Done when: - [ ] Al salir del campo nombre con coincidencias, aparece el panel §3.2 en los 4 formularios (capturas Playwright de cada uno) - [ ] "Crear de todas formas" sobre coincidencia exacta deja el rastro de auditoría (verificado en action_audit_log con psql) - [ ] "Solicitar asociación" crea la solicitud y muestra la confirmación - [ ] UI consume el DS (sin estilos nuevos fuera de tema); build sin errores

Task 10: UI — solicitudes y aprobación en PS/PI con cargo + permisos (Wave 3) — manos Opus

Files: - Edit: /dashboard/perfil-startup y /dashboard/perfil-icg (sección de solicitudes pendientes + pantalla de decisión)

Done when: - [ ] El aprobador ve la tarjeta del solicitante y al aprobar asigna cargo + permisos (startup) o cargo (ICG) en la misma pantalla (capturas Playwright) - [ ] En ICG NO se ofrece permiso de edición (regla dura verificada en la UI y en el server) - [ ] Rechazo pide confirmación y notifica; estados vacíos correctos - [ ] Build y lint sin errores

Task 11: UI — código visible en PS/PI y ficha (Wave 3) — manos Sonnet

Files: - Edit: PS, PI y fichas de entidad (mostrar entity_code con acción copiar)

Done when: - [ ] Código visible en PS, PI y ficha; borradores muestran el estado "se asignará al formalizar" (capturas) - [ ] Sin regresiones visuales en los paneles (comparación antes/después)

Task 12: UI — columna Código en las dos consultas (Wave 3) — manos Sonnet

Files: - Edit: /dashboard/consulta/startups/+page.svelte (+ server) y /dashboard/consulta/icg/+page.svelte (+ server)

Done when: - [ ] Columna "Código" tras el nombre, ordenable, en vacíos, en ambas consultas (capturas Playwright) - [ ] Reglas del DS intactas: Acciones sticky derecha con sombra y medición con skipClass sin romper (scroll horizontal verificado en viewport angosto) - [ ] Build y lint sin errores

Task 13: Integración — flujo REAL completo + deploy (Wave 4) — manos Opus, verificación Fable

Done when: - [ ] Flujo real completo en preview, sin caveats pendientes: registro con nombre duplicado → tarjeta → solicitar asociación → correos a ambas partes → aprobar con cargo+permisos → miembro verificado visible; y el camino "crear de todas formas" → auditoría + código asignado al formalizar - [ ] Recordatorio día 5 y expiración día 10 probados con filas retro-fechadas (y limpiadas) - [ ] Smoke test de las páginas tocadas + deploy verificado por commit SHA (no solo READY) - [ ] Códigos en producción: muestra aleatoria de 10 entidades cumple el formato y la unicidad (psql)


9. Recomendaciones que Frank debe confirmar

  1. (Tu 4b) El AA del código = año de REGISTRO EN EL SISTEMA (fecha de formalización), no año de creación de la empresa. Justificación: (a) siempre se conoce y nunca cambia — el año de fundación puede ser desconocido, disputado o muy antiguo ("1987" en un identificador de sistema no aporta); (b) hace el código verificable contra la base (coincide con formalized_at); (c) es el mismo criterio del código de eventos (año del evento en el sistema); (d) el dato de fundación ni siquiera es un campo confiable/obligatorio hoy. Recomendación: año de registro. ¿Confirmas?
  2. Consecutivo NNNNN por año, compartido entre startups e ICGs (un solo contador anual, como eventos). Alternativa: contador separado por tipo. Recomendamos compartido: números más informativos ("cuántas entidades entraron este año") y el tipo ya está en TTT. ¿Confirmas?
  3. Los 43 códigos cortos propuestos (§5.2): revisa la tabla por si quieres ajustar alguna sigla (es solo cambiar la fila del seed antes de ejecutar).
  4. Correo de expiración a ambas partes (§4.4, última fila): lo agregamos por simetría con tu regla de "ambas partes"; si lo prefieres solo al solicitante, se recorta.
  5. Deuda del codigo_unico SERIAL de eventos (§5.6): viola la regla nueva (se asigna al guardar borrador). Recomendamos registrarla como deuda y corregirla en una tarea aparte (tocarla implica revisar los enlaces públicos de compartir eventos); no la incluimos en el alcance de la 67. ¿De acuerdo?
  6. Data-fix de países legacy ISO2 → nombre (§3.4): es prerrequisito del constraint por país y del prefijo telefónico correcto. Acotado, con snapshot y reversible. Se ejecuta dentro de la Task 3. ¿De acuerdo?

Resumen: el v2 incorpora tus 10 respuestas y además repara un hueco que encontramos al revisarlas: el índice único actual quedó inoperante desde el modelo de formalización de julio, así que el rediseño por países que pediste es también la reparación de la protección base. El código único queda TTT-PPP-AA-NNNNN con prefijo telefónico (criterio de eventos), tipo granular extensible por catálogo (agregar subcategoría = 1 fila) y consecutivo a 5 dígitos, asignado solo al formalizar bajo la regla transversal nueva "nada en borrador recibe código". La aprobación de miembros vive en el PS/PI con cargo + permisos en una sola pantalla, con correos a ambas partes en cada hito. 13 tareas en 5 olas, migraciones aditivas y reversibles con snapshot, alto riesgo con manos Opus y verificación con evidencia. Con tu OK (y tu confirmación de los 6 puntos del §9) arrancamos por la Ola 0.