Modelo vigente
Reemplaza al modelo archivado en _archivo/datos/modelo-datos-legacy.md. Cuando una columna cambie, sincronizar este archivo y propagar a los backlogs P0 de la APK.
Modelo de datos¶
Documento técnico canónico — 2026-06-08 (sustituye a la versión v1)
Este documento es la fuente única de verdad del modelo de datos de IslaBox. Es la versión consolidada y limpia (anteriormente modelo-datos-v2.md).
Esta versión:
- Elimina campos duplicados (ej.
phonerepetido enusers). - Unifica el tipo de
idabigintautoincrement para todas las entidades de negocio. - Alinea todos los enums con estados.md.
- Separa explícitamente tablas MVP vs Fase 2.
- Documenta cada tabla con su propósito, índices y reglas asociadas.
Relación: la versión anterior está archivada como ../_archivo/datos/modelo-datos-legacy.md y no debe usarse como referencia.
1. Convenciones¶
| Símbolo | Significado |
|---|---|
| PK | Primary Key |
| FK | Foreign Key |
| UK | Unique constraint |
| IX | Index (para queries frecuentes) |
| MVP | Tabla/campo disponible en MVP |
| F2 | Solo Fase 2 |
| 🔵 | Soft delete (campo deleted_at) |
| ❗ | NOT NULL |
Tipos estándar¶
| Tipo Postgres | Uso |
|---|---|
bigint |
IDs de negocio (autoincrement) |
uuid |
Solo file_attachments.id (para distribución de IDs) |
varchar(N) |
Texto corto con longitud definida |
text |
Texto largo sin límite |
boolean |
Flags |
decimal(p,s) |
Montos y medidas |
timestamp |
Fecha y hora UTC |
date |
Solo fecha |
enum |
Valores discretos (definidos en Postgres) |
jsonb |
Datos semi-estructurados |
array |
Arreglos de valores |
inet |
Direcciones IP |
Convenciones de nombres¶
- Tablas en plural (
users,physical_packages). - Columnas en snake_case.
- Timestamps:
created_at,updated_at,deleted_at. - Foreign keys:
<recurso>_id(ej.user_id). - Enums: prefijo por tabla + nombre (
package_status,shipment_status).
2. Núcleo de identidad¶
2.1 users (MVP)¶
Personas registradas en la plataforma (usuarios finales y personal interno).
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK, autoincrement |
email |
varchar(255) | ❗ | UK, validado en app |
email_verified |
boolean | ❗ | Default: false |
phone |
varchar(20) | ❗ | UK, formato E.164 con +1 (RN-001) |
phone_verified |
boolean | ❗ | Default: false |
full_name |
varchar(255) | ❗ | |
country_code |
char(2) | ❗ | Default: US, no editable |
password_hash |
varchar(255) | ❗ | bcrypt cost 12 |
role |
enum | ❗ | user / operator / admin / sysadmin |
referral_code |
varchar(20) | ❗ | UK, generado al registrarse |
referred_by |
bigint | ✓ | FK → users.id |
level |
integer | ❗ | Default: 1 (Fase 2) |
is_active |
boolean | ❗ | Default: true |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ | |
deleted_at |
timestamp | ✓ | 🔵 Soft delete |
Índices: email, phone, referral_code, role, is_active, created_at.
2.2 api_tokens (MVP)¶
Tokens JWT emitidos para sesiones.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
token_hash |
varchar(255) | ❗ | UK, SHA-256 del token |
name |
varchar(100) | ❗ | Para identificar (ej. "iPhone de María") |
scopes |
varchar[] | ❗ | Ej: ["read:packages", "write:incidents"] |
last_used_at |
timestamp | ✓ | |
expires_at |
timestamp | ✓ | Default: now() + 30 días |
created_at |
timestamp | ❗ | |
revoked_at |
timestamp | ✓ | Soft revocation |
Índices: user_id, token_hash, expires_at, revoked_at.
2.3 device_tokens (MVP)¶
Tokens FCM/APNS para push notifications.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
token |
varchar(500) | ❗ | UK, el token del push provider |
platform |
enum | ❗ | ios / android / web |
device_id |
varchar(255) | ✓ | Identificador del device |
is_active |
boolean | ❗ | Default: true |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
3. Casillero¶
3.1 locker_codes (MVP)¶
Identificadores únicos de casillero
CP-XXXXXX-D.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
prefix |
varchar(10) | ❗ | Default: CP |
sequential |
integer | ❗ | Secuencial dentro del prefijo |
check_digit |
integer | ❗ | Dígito verificador mod-10 (RN-002) |
full_code |
varchar(20) | ❗ | UK, CP-001042-8 |
status |
enum | ❗ | active / inactive / blocked |
issued_at |
timestamp | ❗ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
Índices: UK(prefix, sequential), UK(full_code), user_id.
Reglas: ver RN-002, RN-003 en reglas-negocio.md.
4. Direcciones y contactos¶
4.1 addresses (MVP)¶
Direcciones de casillero, facturación y destinatarios.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
address_type |
enum | ❗ | locker / billing / recipient |
full_name |
varchar(255) | ❗ | |
phone |
varchar(20) | ❗ | E.164 |
address_line1 |
varchar(500) | ❗ | |
address_line2 |
varchar(500) | ✓ | |
city |
varchar(100) | ❗ | |
state |
varchar(100) | ❗ | |
zip_code |
varchar(20) | ❗ | |
country |
varchar(2) | ❗ | ISO 3166-1 alpha-2 |
province |
varchar(100) | ✓ | Solo Cuba |
municipality |
varchar(100) | ✓ | Solo Cuba |
delivery_references |
text | ✓ | |
is_default |
boolean | ❗ | Default: false |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
4.2 senders (MVP)¶
Remitentes en EE.UU. (opcional, default = datos del usuario).
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id (dueño del registro) |
full_name |
varchar(255) | ❗ | |
phone |
varchar(20) | ❗ | E.164 con +1 |
email |
varchar(255) | ✓ | |
address_line1 |
varchar(500) | ❗ | |
city |
varchar(100) | ❗ | |
state |
varchar(100) | ❗ | |
zip_code |
varchar(20) | ❗ | |
relationship_to_shipment |
varchar(100) | ✓ | |
notes |
text | ✓ | |
created_at |
timestamp | ❗ |
4.3 recipients (MVP)¶
Destinatarios en Cuba.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id (usuario que lo gestiona) |
full_name |
varchar(255) | ❗ | |
identity_document |
varchar(50) | ✓ | CI cubano |
phone |
varchar(20) | ❗ | E.164 con +53 o +1 |
alternate_phone |
varchar(20) | ✓ | |
address_line1 |
varchar(500) | ❗ | Dirección en Cuba |
province |
varchar(100) | ❗ | Provincia cubana |
municipality |
varchar(100) | ❗ | |
delivery_references |
text | ✓ | |
is_default |
boolean | ❗ | Default: true (el propio usuario) |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
5. KYC¶
5.1 kyc_records (MVP)¶
Un KYC por usuario.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id, UK |
document_type |
enum | ❗ | passport / national_id / drivers_license |
document_number |
varchar(50) | ❗ | |
document_front_file_id |
uuid | ✓ | FK → file_attachments.id |
document_back_file_id |
uuid | ✓ | FK → file_attachments.id |
selfie_file_id |
uuid | ✓ | FK → file_attachments.id |
residence_address |
text | ❗ | |
province |
varchar(100) | ❗ | Estado/provincia de residencia |
municipality |
varchar(100) | ❗ | Ciudad/municipio |
zip_code |
varchar(20) | ❗ | |
status |
enum | ❗ | Ver estados.md §KYC |
submitted_at |
timestamp | ✓ | |
reviewed_by |
bigint | ✓ | FK → users.id |
reviewed_at |
timestamp | ✓ | |
rejection_reason |
text | ✓ | |
expires_at |
timestamp | ✓ | Default: now() + 2 años |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
Reglas: ver RN-040 a RN-045.
6. Prealerta (reporte de compra)¶
6.1 purchase_reports (MVP)¶
Reportes de compra antes de que llegue el paquete.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
marketplace |
enum | ❗ | amazon / shein / temu / other |
order_number |
varchar(100) | ❗ | |
total_declared_value |
decimal(10,2) | ❗ | |
total_declared_currency |
varchar(10) | ❗ | Default: USD |
status |
enum | ❗ | Ver estados.md §prealerta |
recipient_id |
bigint | ✓ | FK → recipients.id |
sender_id |
bigint | ✓ | FK → senders.id |
wants_consolidation |
boolean | ❗ | Default: false |
notes |
text | ✓ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
6.2 purchase_items (MVP)¶
Ítems individuales de un reporte de compra.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
purchase_report_id |
bigint | ❗ | FK → purchase_reports.id |
description |
text | ❗ | |
quantity |
integer | ❗ | Default: 1 |
unit_value |
decimal(10,2) | ❗ | |
currency |
varchar(10) | ❗ | Default: USD |
category_id |
bigint | ✓ | FK → customs_categories.id |
product_url |
varchar(1000) | ✓ | |
seller |
varchar(255) | ✓ | |
created_at |
timestamp | ❗ |
6.3 tracking_numbers (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
purchase_report_id |
bigint | ❗ | FK → purchase_reports.id |
tracking_number |
varchar(100) | ❗ | |
carrier |
enum | ❗ | ups / usps / fedex / dhl / other |
estimated_arrival |
date | ✓ | |
last_polled_at |
timestamp | ✓ | Última consulta al carrier (F2) |
created_at |
timestamp | ❗ |
7. Almacén y paquetes¶
7.1 warehouse_locations (MVP)¶
Ubicaciones físicas en el almacén de Miami.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
warehouse_code |
varchar(50) | ❗ | Default: MIA01 |
zone |
varchar(10) | ❗ | A–F (ver manual-almacen §2.6) |
shelf |
varchar(10) | ❗ | |
bin |
varchar(10) | ❗ | |
location_code |
varchar(20) | ❗ | UK, A-03-B |
is_occupied |
boolean | ❗ | Default: false |
created_at |
timestamp | ❗ |
7.2 physical_packages (MVP)¶
Paquete físico recibido en almacén.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
locker_code_id |
bigint | ❗ | FK → locker_codes.id |
purchase_report_id |
bigint | ✓ | FK → purchase_reports.id |
tracking_number_id |
bigint | ✓ | FK → tracking_numbers.id |
warehouse_location_id |
bigint | ✓ | FK → warehouse_locations.id |
status |
enum | ❗ | Ver estados.md §paquete |
received_at |
timestamp | ❗ | |
received_by |
bigint | ❗ | FK → users.id (operador) |
weight_kg |
decimal(8,3) | ✓ | |
length_cm |
decimal(8,2) | ✓ | |
width_cm |
decimal(8,2) | ✓ | |
height_cm |
decimal(8,2) | ✓ | |
volumetric_weight_kg |
decimal(8,3) | ✓ | Calculado |
billable_weight_kg |
decimal(8,3) | ✓ | max(real, volumétrico) |
storage_days |
integer | ❗ | Default: 0, calculado |
storage_charge_total |
decimal(10,2) | ❗ | Default: 0 |
customs_category_id |
bigint | ✓ | FK → customs_categories.id |
customs_declared_value |
decimal(10,2) | ✓ | |
notes_internal |
text | ✓ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
7.3 package_events (MVP)¶
Historial de eventos de un paquete (timeline).
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
physical_package_id |
bigint | ❗ | FK → physical_packages.id |
status |
enum | ❗ | Estado resultante |
description |
text | ✓ | |
operator_id |
bigint | ✓ | FK → users.id |
visible_to_user |
boolean | ❗ | Default: true |
source |
enum | ❗ | operator / user / agency / system |
metadata |
jsonb | ✓ | Datos extra del evento |
created_at |
timestamp | ❗ |
7.4 package_photos (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
physical_package_id |
bigint | ❗ | FK → physical_packages.id |
file_attachment_id |
uuid | ❗ | FK → file_attachments.id, UK |
photo_type |
enum | ❗ | reception / inspection / return / damage / consolidation |
caption |
varchar(255) | ✓ | |
created_at |
timestamp | ❗ |
8. Aduana y categorías¶
8.1 customs_categories (MVP)¶
Catálogo de categorías aduaneras. Tabla canónica (antes había duplicado con
categories).
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
code |
varchar(50) | ❗ | UK, ej. ropa, electronicos |
name |
varchar(100) | ❗ | Nombre visible |
description |
text | ✓ | |
accepted |
enum | ❗ | yes / conditional / no |
requires_invoice |
boolean | ❗ | Default: false |
requires_review |
boolean | ❗ | Default: false |
risk_level |
enum | ❗ | low / medium / high / prohibited |
allows_air |
boolean | ❗ | Default: true |
allows_sea |
boolean | ❗ | Default: true |
duty_percentage |
decimal(5,2) | ✓ | Porcentaje de arancel |
user_message |
text | ✓ | Mensaje al usuario |
operator_message |
text | ✓ | Nota interna |
is_active |
boolean | ❗ | Default: true |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
Tabla categories (legacy)
La tabla categories del modelo v1 queda deprecated y debe eliminarse en migración. La entidad canónica es customs_categories.
9. Consolidación y cotización¶
9.1 consolidations (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
status |
enum | ❗ | Ver estados.md §consolidación |
total_value |
decimal(10,2) | ❗ | |
total_weight_kg |
decimal(8,3) | ❗ | |
recipient_id |
bigint | ❗ | FK → recipients.id |
shipping_method |
enum | ❗ | air / sea |
notes |
text | ✓ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
9.2 consolidation_packages (MVP)¶
Relación N:M entre consolidación y paquetes.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
consolidation_id |
bigint | ❗ | FK → consolidations.id |
physical_package_id |
bigint | ❗ | FK → physical_packages.id, UK con consolidation_id |
added_at |
timestamp | ❗ |
Constraint check: un physical_package no puede estar en 2 consolidaciones activas (no canceladas) al mismo tiempo.
9.3 quotes (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
consolidation_id |
bigint | ❗ | FK → consolidations.id, UK |
total_freight |
decimal(10,2) | ❗ | |
total_customs |
decimal(10,2) | ❗ | |
total_charges |
decimal(10,2) | ❗ | |
total_payable |
decimal(10,2) | ❗ | |
currency |
varchar(10) | ❗ | Default: USD |
valid_until |
timestamp | ❗ | Vigencia 7 días |
status |
enum | ❗ | pending / accepted / expired / superseded |
accepted_at |
timestamp | ✓ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
9.4 quote_items (MVP)¶
Desglose de los conceptos de la cotización.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
quote_id |
bigint | ❗ | FK → quotes.id |
concept |
enum | ❗ | freight / customs_duty / storage / consolidation_fee / service_inspection / service_repack / service_return / insurance / other |
description |
varchar(255) | ❗ | |
amount |
decimal(10,2) | ❗ | |
quantity |
decimal(8,3) | ❗ | Default: 1 |
unit_price |
decimal(10,2) | ✓ |
10. Pagos y facturación¶
10.1 payments (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
quote_id |
bigint | ❗ | FK → quotes.id |
consolidation_id |
bigint | ✓ | FK → consolidations.id |
amount |
decimal(10,2) | ❗ | |
currency |
varchar(10) | ❗ | Default: USD |
payment_method |
enum | ❗ | card / bank_transfer / cash / credit |
payment_provider |
varchar(50) | ✓ | stripe, manual, etc. |
provider_reference |
varchar(255) | ✓ | UK |
status |
enum | ❗ | Ver estados.md §pago |
payment_method_mix |
jsonb | ✓ | Desglose de métodos |
processed_at |
timestamp | ✓ | |
failure_reason |
text | ✓ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
10.2 payment_attempts (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
payment_id |
bigint | ❗ | FK → payments.id |
amount |
decimal(10,2) | ❗ | |
status |
varchar(50) | ❗ | |
provider_response |
jsonb | ✓ | |
created_at |
timestamp | ❗ |
10.3 invoices (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
payment_id |
bigint | ❗ | FK → payments.id, UK |
invoice_number |
varchar(50) | ❗ | UK, formato ISB-2026-00001 |
amount |
decimal(10,2) | ❗ | |
currency |
varchar(10) | ❗ | Default: USD |
pdf_file_id |
uuid | ✓ | FK → file_attachments.id |
issued_at |
timestamp | ❗ | |
created_at |
timestamp | ❗ |
10.4 charges (MVP)¶
Cargos individuales (almacenaje, servicios, etc.) que se incluyen en una cotización.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
consolidation_id |
bigint | ✓ | FK → consolidations.id |
physical_package_id |
bigint | ✓ | FK → physical_packages.id |
charge_type |
enum | ❗ | storage / inspection / repack / customs / delivery / other |
amount |
decimal(10,2) | ❗ | |
description |
text | ❗ | |
status |
enum | ❗ | pending / applied / waived |
applied_at |
timestamp | ✓ | |
created_at |
timestamp | ❗ |
11. Envíos y despacho¶
11.1 shipments (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
consolidation_id |
bigint | ❗ | FK → consolidations.id, UK |
status |
enum | ❗ | Ver estados.md §envío |
carrier |
varchar(100) | ❗ | agencia_aliada en MVP |
master_guide |
varchar(100) | ✓ | UK |
departure_date |
date | ✓ | |
estimated_arrival |
date | ✓ | |
actual_arrival |
date | ✓ | |
customs_amount_total |
decimal(10,2) | ✓ | |
customs_paid_at |
timestamp | ✓ | |
recipient_id |
bigint | ❗ | FK → recipients.id (desnormalizado para histórico) |
notes_internal |
text | ✓ | |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
11.2 shipment_events (MVP)¶
Timeline del envío, alimentado por webhooks de la agencia y operadores.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
shipment_id |
bigint | ❗ | FK → shipments.id |
status |
enum | ❗ | Estado resultante |
description |
text | ✓ | |
location |
varchar(255) | ✓ | |
operator_id |
bigint | ✓ | FK → users.id (si fue manual) |
source |
enum | ❗ | operator / agency / system |
metadata |
jsonb | ✓ | |
created_at |
timestamp | ❗ |
12. Servicios adicionales¶
12.1 service_requests (MVP)¶
Solicitudes de servicios adicionales (inspección, reempaque, devolución).
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
physical_package_id |
bigint | ❗ | FK → physical_packages.id |
service_type |
enum | ❗ | inspection / functional_test / repack / return |
status |
enum | ❗ | Ver estados.md §servicios |
description |
text | ✓ | |
cost |
decimal(10,2) | ✓ | |
cost_approved_at |
timestamp | ✓ | |
started_at |
timestamp | ✓ | |
completed_at |
timestamp | ✓ | |
result |
text | ✓ | |
evidence_file_ids |
uuid[] | ✓ | Array de FK → file_attachments.id |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ |
Constraint: solo puede haber 1 service_request activo por physical_package (ver RN-050).
12.2 additional_services (MVP, lectura)¶
Vista materializada para queries rápidas (alternativa a
service_requestsagregados por paquete). Se calcula al final del sprint.
13. Incidencias¶
13.1 incidents (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ✓ | FK → users.id (si fue el usuario) |
opened_by_operator_id |
bigint | ✓ | FK → users.id (si fue operador) |
resource_type |
enum | ❗ | physical_package / consolidation / shipment / payment / user / other |
resource_id |
bigint | ❗ | ID polimórfico |
incident_type |
enum | ❗ | Ver incidencias-reclamos §2 |
status |
enum | ❗ | Ver incidencias-reclamos §3 |
description |
text | ❗ | |
assigned_operator_id |
bigint | ✓ | FK → users.id |
resolution |
text | ✓ | |
refund_payment_id |
bigint | ✓ | FK → payments.id (si hubo reembolso) |
created_at |
timestamp | ❗ | |
updated_at |
timestamp | ❗ | |
closed_at |
timestamp | ✓ |
13.2 incident_messages (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
incident_id |
bigint | ❗ | FK → incidents.id |
author_id |
bigint | ❗ | FK → users.id |
author_type |
enum | ❗ | user / operator / admin / system |
body |
text | ❗ | |
is_internal |
boolean | ❗ | Default: false (visible al usuario) |
created_at |
timestamp | ❗ |
13.3 incident_evidence (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
incident_id |
bigint | ❗ | FK → incidents.id |
file_attachment_id |
uuid | ❗ | FK → file_attachments.id |
uploaded_by |
bigint | ❗ | FK → users.id |
caption |
varchar(255) | ✓ | |
created_at |
timestamp | ❗ |
14. Notificaciones¶
14.1 notifications (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id |
notification_type |
varchar(50) | ❗ | |
title |
varchar(255) | ❗ | |
body |
text | ❗ | |
data_payload |
jsonb | ✓ | |
channel |
enum | ❗ | push / email / sms / in_app |
status |
enum | ❗ | queued / sent / delivered / failed |
sent_at |
timestamp | ✓ | |
delivered_at |
timestamp | ✓ | |
read_at |
timestamp | ✓ | |
created_at |
timestamp | ❗ |
14.2 notification_delivery_log (MVP)¶
Log inmutable de cada intento de entrega.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
notification_id |
bigint | ❗ | FK → notifications.id |
channel |
enum | ❗ | |
provider |
varchar(50) | ❗ | fcm / sendgrid / twilio |
provider_response |
jsonb | ✓ | |
status |
enum | ❗ | pending / success / failed |
attempt_number |
integer | ❗ | |
error_message |
text | ✓ | |
created_at |
timestamp | ❗ |
14.3 notification_preferences (MVP)¶
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ❗ | FK → users.id, UK |
preferences |
jsonb | ❗ | {type: {push: bool, email: bool, sms: bool}} |
updated_at |
timestamp | ❗ |
15. Archivos¶
15.1 file_attachments (MVP)¶
Registro de todos los archivos subidos a MinIO/S3.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
uuid | ❗ | PK (UUID para distribución) |
owner_type |
enum | ❗ | user / operator / admin / system |
owner_id |
bigint | ❗ | ID del dueño |
bucket |
varchar(100) | ❗ | |
object_key |
varchar(500) | ❗ | Path en MinIO |
original_filename |
varchar(255) | ❗ | |
mime_type |
varchar(100) | ❗ | |
size_bytes |
bigint | ❗ | |
checksum_sha256 |
varchar(64) | ❗ | |
visibility |
enum | ❗ | private / public / signed_only |
uploaded_by |
bigint | ❗ | FK → users.id |
expires_at |
timestamp | ✓ | Para archivos temporales |
created_at |
timestamp | ❗ | |
deleted_at |
timestamp | ✓ | 🔵 |
15.2 file_access_logs (MVP)¶
Auditoría de accesos a archivos sensibles.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
file_attachment_id |
uuid | ❗ | FK → file_attachments.id |
accessed_by |
bigint | ❗ | FK → users.id |
access_type |
enum | ❗ | read / download / upload / delete |
ip_address |
inet | ✓ | |
user_agent |
text | ✓ | |
created_at |
timestamp | ❗ |
16. Webhooks externos¶
16.1 webhook_events (MVP)¶
Idempotencia de webhooks de Stripe y agencia aliada.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
provider |
enum | ❗ | stripe / agency / whatsapp |
event_id |
varchar(255) | ❗ | UK con provider |
event_type |
varchar(100) | ❗ | |
payload |
jsonb | ❗ | Crudo |
signature |
varchar(500) | ❗ | |
processed |
boolean | ❗ | Default: false |
processed_at |
timestamp | ✓ | |
error_message |
text | ✓ | |
retry_count |
integer | ❗ | Default: 0 |
received_at |
timestamp | ❗ | |
created_at |
timestamp | ❗ |
17. Auditoría y logs¶
17.1 audit_logs (MVP)¶
Log inmutable de todas las acciones críticas.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
user_id |
bigint | ✓ | FK → users.id (puede ser null para acciones del sistema) |
action |
varchar(100) | ❗ | package.received, payment.completed, etc. |
entity_type |
varchar(100) | ❗ | |
entity_id |
bigint | ✓ | |
changes |
jsonb | ✓ | {before: {...}, after: {...}} |
ip_address |
inet | ✓ | |
user_agent |
text | ✓ | |
request_id |
varchar(100) | ✓ | UUID del request HTTP |
result |
enum | ❗ | success / failure / forbidden |
created_at |
timestamp | ❗ |
Inmutabilidad
Esta tabla no debe permitir UPDATE ni DELETE. Se replica a S3 WORM mensualmente.
18. Configuración dinámica (admin)¶
18.1 shipping_rates (MVP)¶
Tarifas configurables de envío.
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
method |
enum | ❗ | air / sea |
rate_per_lb |
decimal(8,4) | ❗ | |
currency |
varchar(10) | ❗ | Default: USD |
min_charge |
decimal(10,2) | ❗ | Cargo mínimo |
effective_from |
timestamp | ❗ | |
effective_until |
timestamp | ✓ | Null = vigente |
created_by |
bigint | ❗ | FK → users.id |
created_at |
timestamp | ❗ |
18.2 storage_policies (MVP)¶
Política dinámica de almacenamiento (ver politica-almacenamiento.md).
| Campo | Tipo | Nulo | Notas |
|---|---|---|---|
id |
bigint | ❗ | PK |
name |
varchar(100) | ❗ | UK, ej. standard, no_prealert |
free_days |
integer | ❗ | |
daily_rate |
decimal(8,2) | ❗ | |
abandonment_days |
integer | ❗ | |
abandonment_amount |
decimal(10,2) | ❗ | |
effective_from |
timestamp | ❗ | |
effective_until |
timestamp | ✓ | |
created_by |
bigint | ❗ | FK → users.id |
created_at |
timestamp | ❗ |
19. Fase 2 (no MVP)¶
Las siguientes tablas se reservan para Fase 2 y no se crean en MVP:
| Tabla | Propósito | Trigger para crearla |
|---|---|---|
referrals |
Tracking de referidos | Activación del programa |
credit_balances |
Saldo de crédito | Idem |
credit_movements |
Movimientos de crédito | Idem |
promo_cards |
Tarjetas promocionales | Idem |
user_levels |
Niveles de fidelidad | Idem |
loyalty_pool |
Pool promocional | Idem |
feedback |
Feedback de usuarios | Lanzamiento |
support_tickets |
Tickets de soporte | Lanzamiento Fase 2 |
20. Vistas materializadas¶
20.1 v_package_summary (MVP)¶
Vista desnormalizada que une paquete + casillero + prealerta + consolidación + envío + KYC. Usada por listados y dashboards.
CREATE MATERIALIZED VIEW v_package_summary AS
SELECT
pp.id AS package_id,
pp.status,
pp.received_at,
pp.weight_kg,
pp.billable_weight_kg,
pp.storage_days,
pp.storage_charge_total,
lc.full_code AS locker_code,
u.id AS user_id,
u.full_name AS user_name,
u.email AS user_email,
u.phone AS user_phone,
kr.status AS kyc_status,
pr.marketplace,
pr.order_number,
tn.tracking_number,
tn.carrier,
wl.location_code AS warehouse_location,
c.id AS consolidation_id,
c.status AS consolidation_status,
s.id AS shipment_id,
s.status AS shipment_status,
s.master_guide,
pp.created_at,
pp.updated_at
FROM physical_packages pp
JOIN locker_codes lc ON lc.id = pp.locker_code_id
JOIN users u ON u.id = lc.user_id
LEFT JOIN kyc_records kr ON kr.user_id = u.id
LEFT JOIN purchase_reports pr ON pr.id = pp.purchase_report_id
LEFT JOIN tracking_numbers tn ON tn.id = pp.tracking_number_id
LEFT JOIN warehouse_locations wl ON wl.id = pp.warehouse_location_id
LEFT JOIN consolidation_packages cp ON cp.physical_package_id = pp.id
LEFT JOIN consolidations c ON c.id = cp.consolidation_id
LEFT JOIN shipments s ON s.consolidation_id = c.id;
CREATE UNIQUE INDEX idx_vps_pk ON v_package_summary(package_id);
CREATE INDEX idx_vps_user ON v_package_summary(user_id);
CREATE INDEX idx_vps_status ON v_package_summary(status);
Refrescar con REFRESH MATERIALIZED VIEW CONCURRENTLY v_package_summary cada 5 minutos.
21. Reglas enforced por la base de datos¶
| Regla | Implementación |
|---|---|
| Un usuario tiene un solo KYC | UNIQUE(user_id) en kyc_records |
| Un usuario tiene un solo balance de crédito | UNIQUE(user_id) en credit_balances (Fase 2) |
| Un código de casillero es único | UNIQUE(full_code) en locker_codes |
| El secuencial es único por prefijo | UNIQUE(prefix, sequential) en locker_codes |
| Un paquete no puede estar en dos consolidaciones activas | EXCLUDE constraint en consolidation_packages |
| El crédito no puede ser negativo | CHECK (available_balance >= 0) en credit_balances |
| El dígito verificador se calcula en aplicación | No en DB (en locker_service.py) |
| Un pago solo tiene una factura | UNIQUE(payment_id) en invoices |
| Un consolidation solo tiene un envío | UNIQUE(consolidation_id) en shipments |
audit_logs es inmutable |
Trigger que bloquea UPDATE/DELETE |
22. Diferencias vs modelo v1¶
| # | Cambio | Razón |
|---|---|---|
| 1 | Eliminado campo duplicado phone en users |
Error de merge |
| 2 | Unificado id a bigint en todas las tablas de negocio (excepto file_attachments que usa uuid) |
Consistencia de joins |
| 3 | Renombrado customs_value → customs_declared_value |
Claridad |
| 4 | Renombrado status de customs_amount → eliminado (duplicaba info) |
Limpieza |
| 5 | Renombrado return_status → integrado en service_requests con service_type=return |
Evitar redundancia |
| 6 | Renombrado additional_services → service_requests (más semántico) |
Claridad |
| 7 | Consolidado categories y customs_categories en una sola tabla |
Eliminar duplicación |
| 8 | Nuevos campos: volumetric_weight_kg, billable_weight_kg en physical_packages |
Necesario para cotización |
| 9 | Nuevos campos: payment_method_mix en payments (jsonb) |
Soporte para pago mixto |
| 10 | Nuevas tablas: webhook_events, notification_delivery_log, notification_preferences, charges, quote_items, shipment_events, incident_messages, incident_evidence |
Cubrir nuevos flujos |
| 11 | Tablas de Fase 2 marcadas y movidas a §19 | No se crean en MVP |
23. Próximos pasos¶
- Validar este modelo v2 con el equipo backend antes de implementar.
- Generar las migraciones Alembic a partir de este modelo.
- Eliminar la tabla
categories(legacy). - Eliminar la tabla
additional_services(legacy) tras migrar aservice_requests. - Actualizar
api-contract.mdymatriz-trazabilidad.mdcon los nuevos nombres de campos y tablas.