Proyecto: LPDI SGR (eco.lpdi.co) · Fecha: 23 de julio de 2026 · Estado: ANÁLISIS Y PLAN — pendiente de tu aprobación antes de ejecutar
Cómo se crea hoy una entidad: el usuario entra a /registro-startup, /registro-icg (público) o al dashboard; el servidor inserta la fila en estado DRAFT; al "Registrar y continuar" se formaliza y queda activa. No hay flujo de aprobación: nadie revisa si la entidad es legítima o duplicada.
Qué protección contra duplicados existe hoy:
| Mecanismo | Qué hace | Qué NO hace |
|---|---|---|
Índice único (registrador, nombre) excluyendo borradores |
Impide que el mismo usuario registre dos veces el mismo nombre | No impide que dos usuarios distintos registren la misma empresa — este es el hueco que describes |
/api/check-company-name |
Busca coincidencia exacta (sensible a tildes) | No busca en el nombre legal, no cruza startups contra ICGs, no detecta variantes ("Acme SAS" vs "Acme S.A.S.") |
| Aviso ámbar en los formularios | "Ya existe una empresa con este nombre" | Es puramente informativo: no bloquea, no muestra cuál es, no ofrece alternativa. Se puede ignorar |
Diagnóstico: el sistema está expuesto hoy. A nivel de base de datos nada impide que dos usuarios creen "Acme" dos veces; a nivel de interfaz solo hay un aviso ignorable. Consecuencias: fichas fragmentadas de la misma empresa, métricas infladas, y confusión en perks y eventos. A favor: ya existen columnas normalizadas (lower + unaccent) que podemos reutilizar para el matching.
¿Mitiga el problema? Sí, sustancialmente. Convierte el aviso pasivo en una decisión consciente (crear nueva vs. solicitar asociación), y la aprobación por el admin de la entidad existente es una verificación humana de pertenencia más robusta que validar dominios de correo (muchas startups tempranas usan Gmail).
Lo que hay que aceptar o complementar:
j***@acme.com, dominio visible) — basta para confirmar "sí, es mi empresa" sin regalar el correo. (Decisión 1 abajo.)Veredicto: la propuesta es correcta y ejecutable. Se recomienda aprobarla con dos ajustes: correos enmascarados en la tarjeta, y auditoría de los "crear de todas formas" sobre coincidencia exacta.
Detección de coincidencias (nuevo endpoint): busca contra startups e ICGs (una startup nueva puede chocar con un ICG y viceversa), en dos niveles: (1) coincidencia exacta normalizada (sin tildes, "Ácme S.A.S" ≡ "acme s.a.s"); (2) coincidencia aproximada por similitud de texto (captura "Acme SAS" vs "Acme S.A.S.", errores de tipeo). Compara nombre comercial, nombre legal y nombre de ICG, tal como pediste.
UI en el registro: al salir del campo de nombre, si hay coincidencias se abre un panel con tarjetas:
Encontramos entidades parecidas a "Acme"
┌────────────────────────────────────────────┐
│ ACME S.A.S. (Startup) · Colombia │
│ Nombre legal: Acme Tecnología S.A.S. │
│ Admin: 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]
Flujo de solicitud de asociación (invitación inversa): reutiliza los patrones ya probados del sistema (invitaciones de equipo con token y correos, transferencias de entidad). Nueva tabla entity_join_requests. 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 (los contactos ICG nunca editan, regla dura tuya). Ciclo: el solicitante pide → correo a los aprobadores con su tarjeta → el aprobador acepta o rechaza. Si acepta, entra como miembro verificado (ambas partes consintieron); el aprobador decide si con permiso de edición (default: sin). Expira a 10 días con recordatorio al día 5. El solicitante debe estar verificado (Opción B) para poder enviar la solicitud.
Imitando el generador de códigos de eventos (atómico, sin colisiones): formato TT-PPP-AA-NNNN → ST-170-26-0042 (startup #42, Colombia, 2026), IC-604-26-0007 (ICG, Perú).
TT = tipo (ST startup, IC ICG) · PPP = país ISO numérico · AA = año de formalización · NNNN = consecutivo por tipo+año.Todas las migraciones: aditivas, idempotentes y reversibles, con snapshot previo (Supabase sin PITR). Zonas de alto riesgo (migraciones + RLS recursiva) con manos Opus y verificación con evidencia.
| Ola | Tareas | Contenido |
|---|---|---|
| 0 (alto riesgo) | 1–3 | Migraciones: matching de similares, tabla de solicitudes de asociación, código único + backfill |
| 1 | 4–6 | Backend: endpoint de similares (con correos enmascarados), lib de solicitudes de asociación + correos, asignación de código al formalizar |
| 2 | 7–9 | UI: modal de coincidencias en los 4 formularios, pantalla de gestión de solicitudes, mostrar el código único |
| 3 | 10 | Integración: flujo REAL completo en preview + smoke + deploy verificado por SHA |
Cada tarea lleva su "Done when" verificable (tests, idempotencia probada con psql, capturas Playwright, deploy por commit).
j***@acme.com)? Recomiendo enmascarados, por tu propia regla anti-extracción del 14-jul. El nombre completo sí se muestra.TT-PPP-AA-NNNN (ej. ST-170-26-0042)? ¿O sin país (ST-26-0042), o consecutivo global compartido entre startups e ICGs?Resumen: hoy nada impide que dos usuarios registren la misma empresa; solo hay un aviso ignorable. Tu propuesta la convierte en una decisión consciente con verificación humana, y la consideramos correcta. La implementamos reutilizando infraestructura ya probada del sistema, en 10 tareas y 4 olas, con migraciones solo aditivas y reversibles. Con tu OK a las 7 decisiones arrancamos por la Ola 0.