# ANALYSIS.md — Razonamiento de diseño

## Contexto de partida

ONEtoONE Corporate Finance es una firma internacional especializada en asesoramiento en fusiones y adquisiciones (M&A), que opera apoyándose en una red global de asesores y en el uso de bases de datos de empresas e inversores.

La compañía dispone de:
- una base de datos relacional de empresas con sus dominios web
- una base de datos no relacional con el contenido de dichas webs indexado
- herramientas internas para la gestión de procesos de compraventa, entre ellas **Clarity**: app propia orientada al cliente para que consulte el estado de su mandato, los inversores contactados y la documentación del proceso

La necesidad concreta: cuando un asesor consulta una ficha de empresa en la plataforma, debe ver automáticamente qué otros usuarios de la red ya tienen relación con esa empresa, cuándo fue el último contacto, y quién es el interlocutor dentro de la empresa.

**Distinción clave respecto a Clarity:**

ONEtoONE ya dispone de Clarity, una app orientada al cliente que le permite seguir el estado de su mandato: qué inversores se han contactado, en qué fase está el proceso, qué documentación está disponible. Es transparencia de cara a fuera.

Este sistema resuelve un problema distinto y previo: la inteligencia de red interna. Antes de que empiece ningún mandato, o cuando se evalúa si arrancar uno, el equipo necesita saber si alguien de la red ya tiene relación con esa empresa y con qué profundidad. Ese conocimiento estaba disperso en los correos y agendas de cada asesor, invisible para el resto.

- **Clarity** → *"cliente, esto es lo que estamos haciendo por ti"* (post-mandato)
- **Este sistema** → *"asesor, esto es lo que ya sabe tu red sobre esta empresa"* (pre-mandato)

---

## 1. Supuestos de partida

- Las interacciones históricas (emails, reuniones, llamadas, eventos) están registradas con al menos uno de estos datos: dirección de email del contacto, o nombre de empresa.
- Cada interacción tiene un tipo (`reunion`, `llamada`, `email`, `evento`) que refleja la profundidad del contacto.
- Las empresas tienen dominio web canónico en la base de datos relacional, y un estado actual de la relación (`stage`).
- El contenido web indexado permite extraer nombres de personas vinculadas a cada empresa.
- Los usuarios de la plataforma tienen ID único, nombre, email y teléfono.
- Para el MVP, todo esto se simula con datos estáticos en PHP. En producción se conecta a las bases de datos reales y opcionalmente a las APIs de IA disponibles.

**Sobre el origen real de las interacciones:**

El enunciado no especifica cómo se registran las interacciones en el sistema. Dado que ONEtoONE opera con Google Workspace (Gmail, Google Calendar), la hipótesis más razonable es que la fuente de verdad son las propias herramientas de comunicación de los asesores:

- **Gmail API** — permite extraer el historial de emails de cada asesor con contactos externos. Un email cuyo dominio coincide con el de una empresa target constituye una interacción de tipo `email`.
- **Google Calendar API** — permite extraer reuniones y llamadas programadas con asistentes de empresas externas. Una reunión con alguien de `acme.com` constituye una interacción de tipo `reunion`.

Esto presupone un proceso de ingesta que, de forma periódica o en tiempo real, consulta estas APIs, clasifica las interacciones y las persiste en la base de datos interna.

**Rol de Claude / Gemini en la ingesta:**

El dato crudo de Gmail/Calendar no está estructurado. Ahí es donde encajan las APIs de IA disponibles:

- Clasificar el tipo de interacción a partir del asunto o cuerpo del email cuando no es evidente
- Normalizar el nombre de empresa si el asesor lo ha escrito de formas distintas en distintas interacciones ("Acme", "Acme Ltd", "ACME Corp")
- Extraer el nombre y cargo del contacto a partir de la firma del email o la descripción del evento

Este proceso de ingesta es externo al MVP. `InteractionRepository` abstrae completamente esta fuente: sustituir el array PHP por consultas a la BD que el proceso de ingesta rellena no requiere tocar ningún servicio ni controlador.

---

## 2. Soluciones de matching planteadas

### Opción A — Matching por dominio de email

**Cómo funciona:** se extrae el dominio de cada email registrado en las interacciones y se compara con el dominio de la empresa consultada.

| Ventajas | Inconvenientes |
|---|---|
| Determinista y explicable | No cubre interacciones registradas solo por nombre de empresa |
| Alta precisión (el dominio es canónico) | Falla si el contacto usó email personal |
| Sin riesgo de falsos positivos | Sin fallback si no hay email |

---

### Opción B — Búsqueda por nombre de empresa

**Cómo funciona:** se indexan los campos de texto de las interacciones y se busca el nombre de la empresa (exacto o fuzzy).

| Ventajas | Inconvenientes |
|---|---|
| Cubre interacciones sin email | Mayor riesgo de falsos positivos |
| Útil para datos históricos legacy | Requiere normalización ("Acme", "Acme Ltd", "ACME Corp") |
| No depende del dominio | Difícil de auditar |

---

### Opción C — Híbrida: dominio + nombre + enriquecimiento opcional ✅ Seleccionada

**Cómo funciona:**
1. Primero matching por dominio de email (alta precisión)
2. Si no hay email, matching por nombre de empresa (cobertura de fallback)
3. Para identificar el contacto concreto: cruza los nombres de las interacciones con los extraídos del contenido web indexado

| Ventajas | Inconvenientes |
|---|---|
| Máxima cobertura con precisión controlada | Algo más de lógica que las opciones anteriores |
| Cada paso es auditable y explicable | La calidad del enriquecimiento depende del contenido web |
| El enriquecimiento es opcional y degrada con gracia | Requiere dominio canónico en la base de datos |
| Refleja cómo funciona realmente el dato en M&A | |

**Por qué esta:** porque en M&A la explicabilidad no es opcional. Un asesor no puede confiar en un score si no entiende de dónde viene. Esta opción permite mostrar exactamente qué interacciones se detectaron y por qué se vincularon a la empresa.

---

## 3. Elección de stack y uso del servidor ocioso

### Sobre el servidor ocioso

El enunciado menciona un servidor ocioso donde se puede instalar cualquier software gratuito. Se han evaluado tres enfoques:

**Opción A — Montar un stack complejo (Elasticsearch, Redis, Node.js...)**
Técnicamente potente pero innecesario para el MVP. Instalar y mantener infraestructura adicional tiene coste operativo y no aporta valor si el problema puede resolverse con herramientas más simples.

**Opción B — Solución SaaS / cloud (Google Cloud Run, AWS Lambda...)**
ONEtoONE ya es cliente de Google Cloud y AWS, por lo que sería viable. Sin embargo, implica coste variable por uso y dependencia de terceros para una funcionalidad interna. Adecuado para escalar, no para un MVP.

**Opción C — PHP + Apache sobre el servidor disponible** ✅ *Elegida*
El servidor ocioso puede levantar esta solución con una sola línea:
```bash
apt install php8.3 apache2
```
Sin contratar nada, sin configuración compleja, sin tocar otros servicios del servidor. Si el equipo ya trabaja con PHP (perfil del puesto: PHP + MySQL + JS), el mantenimiento es inmediato para cualquier desarrollador de la empresa.

**Por qué esta:** máxima autonomía con mínima fricción. El servidor ocioso pasa a ser productivo sin coste adicional ni dependencias externas. La solución puede crecer (MySQL, Redis, Docker) de forma incremental si la demanda lo justifica.

---

### Sobre la elección de PHP frente a otras alternativas

Se consideraron Python (Flask/FastAPI) y Node.js como alternativas. Ambos son válidos técnicamente, pero PHP es la elección más coherente por tres razones:

1. **Perfil del equipo**: el stack solicitado es PHP + MySQL + JS. Entregar en PHP garantiza que el equipo puede mantener, extender y auditar el código sin curva de aprendizaje.
2. **Infraestructura existente**: cualquier servidor con Apache o Nginx ya tiene soporte nativo para PHP. No hay que instalar ni gestionar un proceso adicional (como haría Node o Gunicorn).
3. **Sin framework deliberadamente**: se usa PHP puro para demostrar comprensión real de la arquitectura por capas. En producción se usaría Laravel, que el equipo ya conoce si trabaja con PHP.

**Sobre MySQL/PDO:** los repositorios están diseñados para sustituirse de forma independiente. Cada uno tiene un contrato mínimo (`all()`, `findByDomain()`, `findById()`) que en producción ejecutaría un prepared statement PDO contra MySQL o MariaDB. Ningún servicio ni controlador requeriría cambios — solo la implementación interna del repositorio.

**Sobre JavaScript:** la capa frontend usa vanilla ES5+ sin dependencias. `public/js/app.js` cubre toggle de formato de fecha (persistido en `localStorage`), selector de idioma (cookie + gettext en PHP), ordenación del desglose de puntuación por fecha y por puntos, y smooth scroll con compensación de header sticky. Todo encapsulado en IIFE con `'use strict'`, manipulación directa del DOM sin jQuery ni frameworks.

---

### Componentes instalados en el servidor

| Software | Licencia | Para qué |
|---|---|---|
| PHP 8.3 | PHP License (libre) | Lenguaje de la aplicación |
| Apache 2.4 | Apache License 2.0 (libre) | Servidor web |
| PHP-PDO + php-mysql | PHP License (libre) | Conexión a BD relacional existente |
| PHP-gettext | PHP License (libre) | Internacionalización (5 idiomas) |
| Composer | MIT (libre) | Gestión de dependencias PHP |
| PHPUnit 11 | BSD (libre) | Tests unitarios (solo desarrollo) |
| Docker *(opcional)* | Apache 2.0 (libre) | Despliegue alternativo sin tocar el SO base |

Todo el software es gratuito y de código abierto. El coste del servidor es cero (ya existía ocioso).

---

### Sobre Docker

Se incluye como alternativa de despliegue. En el servidor ocioso, Docker permite levantar el entorno completo con `make install` sin modificar la configuración base del sistema operativo — útil si el servidor ya tiene otros servicios y se quiere evitar conflictos. No es obligatorio: con PHP + Apache instalados directamente la aplicación funciona igual.

---

## 4. Modelo de puntuación

El score mide la solidez de la relación entre un asesor de la red y una empresa target. Es aditivo e intencionalmente transparente: cada punto tiene una justificación de negocio que el equipo puede ajustar sin tocar código, solo modificando las constantes de `ScoreCalculator`.

### Puntos por tipo de interacción

| Tipo | Puntos | Justificación |
|---|---|---|
| `reunion` | +15 | Reunión presencial o videoconferencia: máxima profundidad relacional |
| `llamada` | +10 | Llamada telefónica: contacto directo pero sin presencia física |
| `email` | +7 | Intercambio de correo: documentado, pero menor compromiso personal |
| `evento` | +5 | Contacto en evento o conferencia: primer punto de contacto |
| Desconocido | +10 | Fallback para datos legados sin tipo registrado |

La ponderación refleja una realidad operativa de M&A: el valor de una relación no es solo cuántas veces ha habido contacto, sino qué tipo de contacto. Una reunión directa con el CFO pesa mucho más que haber intercambiado emails automatizados.

### Bonificaciones por calidad de la relación

| Regla | Puntos | Justificación |
|---|---|---|
| Alguna interacción en los últimos 12 meses | +20 | En M&A, la recencia es el factor más crítico. Una relación de hace 5 años sin actividad reciente vale poco operativamente. |
| Interacciones en más de un año natural | +15 | La continuidad distingue una relación duradera de un contacto puntual |
| Mismo contacto repetido 2+ veces | +10 | Profundidad de relación: importa más que la amplitud |
| Contacto identificado (nombre conocido) | +5 | La inteligencia accionable tiene mayor valor operativo |

### Etiquetas de score

| Rango | Etiqueta | Interpretación |
|---|---|---|
| ≥ 50 | `high` | Relación consolidada con interlocutor conocido. Candidato prioritario para hacer la introducción. |
| 25–49 | `medium` | Relación existente pero sin la profundidad o recencia suficiente para garantizar acceso directo. |
| 0–24 | `low` | Contacto puntual o muy antiguo. Más contexto antes de apoyarse en esta relación. |

El campo `score_breakdown` en el output devuelve la lista de reglas aplicadas. Esto permite que el sistema sea auditable por el asesor sin necesidad de acceder al código.

---

## 5. Estado de la relación por empresa (stage)

Cada empresa en la base de datos tiene un campo `stage` que refleja el estado actual de la relación operativa de ONEtoONE con ella:

| Valor | Significado operativo |
|---|---|
| `prospectando` | Empresa en radar. Sin contacto formal iniciado. |
| `en_conversacion` | Hay conversaciones activas pero sin mandato firmado. |
| `mandato` | Operación activa. ONEtoONE tiene mandato de asesoramiento. |
| `cerrado` | Operación finalizada (éxito o no). |
| `descartado` | Empresa descartada del pipeline activo. |

El stage es visible en la interfaz junto al nombre de la empresa, permitiendo al asesor entender el contexto antes de actuar. En la API se incluye dentro de `company_meta`.

---

## 6. Camino a producción

El MVP está diseñado para que cada pieza sea sustituible de forma independiente:

| Componente actual | Evolución en producción |
|---|---|
| Arrays PHP en `/data/` | Consultas PDO a MySQL/PostgreSQL |
| `data/interactions.php` simulado | BD alimentada por proceso de ingesta desde Gmail API + Calendar API (Google Workspace) |
| `web_content.php` simulado | Integración con base de datos no relacional existente |
| `ContactExtractor` simple | Mejora con Claude API / Gemini / OpenAI para extracción de nombres y cargos del contenido web indexado |
| Sin autenticación | Google Workspace SSO (OAuth2) + JWT para la API |
| Sin caché | Redis con TTL por empresa (el score raramente cambia en minutos) |
| Sin logs | Sistema de logging para trazabilidad de consultas |
| Tests solo en ScoreCalculator | Tests de integración para controladores y repositorios |
| Sin stage workflow | Gestión del ciclo de vida del stage en el CRM interno |

**Enfoque recomendado: registro en tiempo real**

La forma correcta de mantener la BD actualizada es registrar cada interacción en el momento en que ocurre, desde la herramienta interna que ya use el asesor. Clarity está orientada al cliente (transparencia sobre el mandato), no al registro interno de contactos del asesor; el flujo de registro debería integrarse en el CRM o herramienta interna equivalente que use el equipo. Si el dato entra cuando se produce, la BD siempre está al día y no hace falta ningún proceso de minería retroactiva.

Este flujo de registro en tiempo real queda fuera del alcance del MVP y requiere diseñar la integración con el flujo de trabajo habitual del asesor antes de implementarla.

**Pipeline de ingesta retroactiva (solo para carga inicial o demo) No incluido en el MVP:**

```
Gmail API + Calendar API (Google Workspace)
        ↓
  Proceso de ingesta puntual (carga histórica)
        ↓
  Claude / Gemini — clasificación del tipo de interacción,
                    normalización del nombre de empresa,
                    extracción de contacto desde firma/descripción
        ↓
  BD relacional — tabla interactions
        ↓
  InteractionRepository (sin cambios en el código de la aplicación)
```

⚠️ Este pipeline tiene coste real: la Gmail API y Calendar API son gratuitas dentro de Google Workspace, pero Claude cobra por token procesado. Ejecutarlo de forma periódica y masiva generaría un gasto proporcional al volumen de emails de toda la red. Por eso se recomienda usarlo solo para la migración inicial y sustituirlo por registro en tiempo real en producción.

El script `scripts/gmail_ingestion_example.php` muestra técnicamente cómo funcionaría esta ingesta.

Este diseño mantiene la separación de responsabilidades: la lógica de negocio (scoring, matching) no sabe nada del origen del dato. Solo le importa que la interacción esté bien formada cuando llega al repositorio.

**Sobre el uso de IA para extracción de contactos:**

El `ContactExtractor` actual es determinista: cruza nombres de las interacciones con el contenido web indexado. En producción, la mejora natural es conectar el `WebContentRepository` a la base de datos no relacional existente y procesar el contenido con Claude API, Gemini o OpenAI para extraer nombres y cargos de forma semántica. El repositorio abstrae esta fuente completamente: cambiar la implementación no requiere tocar ningún servicio ni controlador. El diseño está preparado para esta evolución desde el día uno.

**Documentación técnica adicional entregada:**

- `ARCHITECTURE.md` — inventario completo de clases, métodos y grafo de dependencias entre capas
- `data/postman_example/onetoone.postman_collection.json` — colección Postman lista para importar con los tres endpoints cubiertos

**Infraestructura mínima para el servidor disponible:**

```bash
# Opción A — PHP + Apache directo (Debian)
apt install php8.3 php8.3-pdo php8.3-mysql apache2
a2enmod rewrite
# Configurar VirtualHost apuntando a /public/
# Conectar a BD existente via PDO en los Repositories

# Opción B — Docker (sin tocar el sistema operativo base)
make install   # construye imagen y levanta en http://localhost:8080
```

---

## 7. Consultas de ejemplo

```
GET /api.php?domain=acme.com
GET /api.php?domain=globex.com
GET /api.php?domain=dundercapital.com
GET /api.php?id=1
GET /api.php?stats=top_users&limit=5
```

Para la interfaz visual:
```
http://localhost:8080/view.php?domain=acme.com
http://localhost:8080/users.php?id=2
```
