CUIT/CUIL生成器和验证器 | 阿根廷税务识别号工具
生成有效的阿根廷CUIT/CUIL测试号码或验证现有号码。免费在线工具,供开发人员测试税务识别和工资系统,并符合AFIP验证标准。
CUIT/CUIL生成器
输入8位DNI号码或使用随机生成器
使用随机生成的DNI
文档
阿根廷 CUIT/CUIL 生成器和验证器
简介
在阿根廷的税务和劳动系统中,你会经常遇到两个关键标识符:CUIT(税务统一识别码)和 CUIL(劳动统一识别码)。可以将CUIT视为企业和纳税个人的税务识别号,而CUIL则用于识别社会保障系统中的工作者。这两种识别码都使用相同的11位数字格式和验证算法,这使得测试处理这些数字的应用程序变得尤为重要。
在为阿根廷市场开发软件时,你需要有效的测试数据。这就是本工具的用途。你可以生成在数学上正确的CUIT/CUIL号码用于测试,而无需使用真实的个人信息,或验证现有号码以确保它们符合AFIP的官方算法。特别有用的是,该工具同时处理生成端(按需创建有效号码)和验证端(在导致生产问题之前捕捉格式错误、无效类型代码和不正确的验证数字)。
CUIT/CUIL 结构和计算
理解格式
有效的 CUIT/CUIL 号码由 11 位数字组成,通常以 XX-XXXXXXXX-X 的格式显示。以下是详细解析:
-
类型代码(前 2 位):标识实体类型
- 20, 23, 24:男性个人(CUIL)
- 27:女性个人(CUIL)
- 30, 33, 34:公司和组织(CUIT)
-
DNI 号码(中间 8 位):对于个人,这是他们的国家身份证件号码(DNI)。常见的陷阱是:如果实际 DNI 少于 8 位,必须在前面补零。例如,DNI 1234567 在 CUIT/CUIL 中变为 01234567。公司则接收一个唯一的分配号码。
-
验证数字(最后一位):这个校验数字使用加权算法验证整个号码。如果这个数字与计算不匹配,整个 CUIT/CUIL 就是无效的——这常常会让开发人员在手动构建测试数据时措手不及。
验证数字计算
验证数字使用模数-11 加权算法。工作原理如下:
- 取 CUIT/CUIL 的前 10 位(类型代码 + DNI)
- 将每位数字乘以对应的权重:5, 4, 3, 2, 7, 6, 5, 4, 3, 2
- 求所有乘积的和
- 计算:11 减去(和模 11)
- 处理边界情况:如果结果是 11,使用 0;如果是 10,使用 9
最容易让人困惑的是步骤 5 中的边界情况。在测试时,你偶尔会得到 10 或 11 的结果,如果不知道要将这些映射到 9 和 0,就会导致验证失败,这种情况很难调试。
从数学上可以表示为:
其中:
- 是验证数字
- 是前 10 位中的第 位数字
- 是来自序列 [5, 4, 3, 2, 7, 6, 5, 4, 3, 2] 的对应权重
- 特殊情况:如果 则 ,如果 则
分步指南
使用生成器
-
选择界面顶部的"生成器"选项卡。
-
从下拉菜单中选择类型代码:
- 20:男性(CUIL)
- 23:企业(CUIT)
- 24:临时企业(CUIT)
- 27:女性(CUIL)
- 30:公司(CUIT)
- 33:民间社团(CUIT)
- 34:基金会(CUIT)
-
输入DNI号码(可选):
- 在提供的字段中输入8位DNI号码
- 如果留空,工具将使用随机生成的DNI
- 如果输入少于8位数字,系统将在前面补零
-
生成随机DNI(可选):
- 点击"随机"按钮生成随机的8位DNI号码
-
查看生成的CUIT/CUIL:
- 工具根据您的输入自动显示有效的CUIT/CUIL
- 格式将为XX-XXXXXXXX-X,并带有正确的验证数字
-
复制结果:
- 点击复制图标将生成的CUIT/CUIL复制到剪贴板
- 成功复制时将出现确认消息
使用验证器
-
选择界面顶部的"验证器"选项卡。
-
输入要验证的CUIT/CUIL:
- 以XX-XXXXXXXX-X格式输入CUIT/CUIL
- 工具将在您输入时自动使用连字符格式化输入
- 您也可以输入不带连字符的号码(XXXXXXXXXXX)
-
点击"验证"按钮:
- 工具将检查格式、类型代码和验证数字
-
查看验证结果:
- 对于有效的CUIT/CUIL号码,将出现绿色成功消息
- 对于无效号码,将出现红色错误消息并解释问题:
- 无效格式(必须为XX-XXXXXXXX-X)
- 无效类型代码(必须为:20、23、24、27、30、33、34之一)
- 无效验证数字
-
附加信息:
- 对于有效号码,工具将显示组件的详细信息:
- 类型代码及其含义
- DNI号码
- 验证数字
- 对于有效号码,工具将显示组件的详细信息:
常见错误和最佳实践
在应用程序中处理 CUIT/CUIL 验证时,请注意以下常见陷阱:
零填充问题:我遇到的最常见的错误涉及带有前导零的 DNI 号码。当有人输入 DNI "1234567" 时,您的代码需要将其视为 "01234567" 以构建 CUIT/CUIL。许多实现在处理时将其转换为整数,这会去除前导零。请改用带有适当填充的字符串操作。
类型代码混淆:并非所有两位数都是有效的类型代码。只有 20、23、24、27、30、33、34 是有效的。我见过接受任何两位数前缀的系统,导致像 21-12345678-X 这样看似有效但未通过 AFIP 验证的数字。在计算验证数字之前,始终验证类型代码。
性别假设:类型代码 20(男性)和 27(女性)不可互换。如果您基于人口统计数据自动生成 CUIL,请确保您的类型代码与 RENAPER(阿根廷国家登记处)注册的实际性别相匹配。不匹配会导致个人在使用政府服务时被拒绝。
连字符处理:某些实现需要连字符,而其他实现则拒绝连字符。最佳实践:在输入字段中接受两种格式(有或无连字符),在验证前去除连字符,在数据库中存储无连字符的版本,并仅在显示时添加连字符。
缺少验证:算法验证可以捕获格式错误,但不能确认 CUIT/CUIL 已注册。在生产环境中,先验证格式(快速),然后针对 AFIP 的数据库进行验证(较慢但权威)。这种两阶段方法可以立即捕获拼写错误,同时确保号码是真实的。
边缘情况测试:始终使用验证数字 0 和 9 进行测试 - 这些是算法中特殊情况的结果。还要测试以零开头的 DNI,以确保您的填充逻辑正常工作。
用例和实际应用
开发和测试
软件开发:在为阿根廷市场构建应用程序时,您需要通过验证的测试数据,而不使用真实的用户信息。以下是关键场景:
- 电子商务平台:支付处理器和发票系统需要有效的CUIT/CUIL号码。在负载测试期间,您可能需要数千个有效号码来模拟真实的用户注册。
- 会计软件:税务报告模块在提交给AFIP之前验证CUIT/CUIL格式。测试这些验证规则需要通过校验位算法的号码。
- 人力资源管理系统:工资处理和社会保障缴费依赖于有效的CUIL号码。常见场景是:在测试员工入职流程时,需要验证无效的CUIL是否触发适当的错误消息。
- 银行应用:KYC(了解您的客户)流程将CUIT/CUIL作为开户的一部分进行验证。根据我的经验,类型代码不匹配等边缘情况常常在集成测试中暴露出来。
数据库填充:在填充测试数据库时,使用算法有效的CUIT/CUIL号码可确保您的数据库约束和触发器按预期工作。这在测试数据迁移脚本或验证识别号码的ETL流程时尤为重要。
表单验证测试:测试正常路径和错误情况。生成有效号码以确认成功提交,然后修改验证数字,确保您的前端和后端验证在数据进入数据库之前捕获错误。
API测试:集成测试应在请求负载中使用有效的CUIT/CUIL号码。这可以防止由于无效测试数据而不是API逻辑中的实际错误导致的误报。
QA自动化:参数化测试受益于动态CUIT/CUIL生成。与其硬编码可能在测试运行间冲突的测试值,不如为每次测试执行生成新的有效号码。
教育目的
学习验证算法:CUIT/CUIL验证算法展示了模运算和加权校验位的实际应用。这种模式在全球其他识别系统中也很常见——信用卡号码(Luhn算法)、ISBN代码和欧洲增值税号码都使用类似的加权校验和方法。了解CUIT/CUIL验证的工作原理,可以洞察这些算法存在的原因:它们能捕获转录错误,如数字转置或单个数字错误。
教授数据验证:在培训初级开发人员进行输入验证时,CUIT/CUIL系统提供了一个比电子邮件验证更复杂但比加密验证更简单的真实世界示例。它展示了深度防御原则:格式验证(11位数字)、业务规则验证(有效类型代码)和算法验证(校验位)。
国际商业发展:如果您正在为拉丁美洲市场构建系统,了解CUIT/CUIL将帮助您理解邻国的类似系统。巴西有CPF/CNPJ,智利有RUT,乌拉圭有RUT——都使用校验位算法。您在这里学到的模式可以应用到这些系统中。
替代方案和生产考虑
虽然此工具适用于开发和测试,但生产环境有不同的要求:
官方政府验证:对于实时应用,仅凭算法验证是不够的——您需要验证CUIT/CUIL是否已在AFIP(公共收入管理局)注册。AFIP提供实时验证的Web服务,尽管您需要注册和认证才能访问。关键是:一个号码可能在数学上有效,但未正式分配,因此在生产中始终实施这两层验证。
库和包:如果要将CUIT/CUIL验证集成到代码库中,请考虑使用这些成熟的库,而不是自己开发:
- JavaScript:npm上的
validar-cuit处理验证和格式化 - PHP:
afip-php库提供验证和AFIP Web服务集成 - Python:PyPI上的
py-cuit实现了完整算法
使用维护良好的库可以降低实现错误的风险,并且通常包含您在自定义代码中可能遗漏的边缘情况。
手动计算:出于学习或调试目的,手动完成算法有助于加深理解。拿起计算器和一个CUIT/CUIL示例,然后逐步遵循加权乘法过程。
企业验证服务:大规模应用可能受益于第三方验证API,不仅检查格式,还可以交叉引用商业数据库。这些服务可以标记非活跃企业、识别受制裁实体,或提供关于CUIT/CUIL持有者的其他元数据。
CUIT/CUIL系统的历史
阿根廷的CUIT/CUIL识别系统自其inception以来已经发生了重大变化:
起源和实施
CUIT(统一税务识别码)最初于1970年代在阿根廷引入,作为现代化税收征集系统的一部分。公共收入联邦管理局(AFIP)实施这一唯一标识符,以更有效地追踪纳税人并减少税收逃避。
CUIL(统一劳动识别码)随后被引入,专门用于识别社会保障系统中的工人,在保持一致格式的同时,在税务识别和劳动识别之间创建了区分。
演变和数字化
在1990年代,随着阿根廷经历重大经济改革,CUIT/CUIL系统对追踪经济活动变得越来越重要。该系统进一步数字化,并实施了在线验证系统。
2000年代初期,CUIT/CUIL系统与各种数字政府服务整合,成为阿根廷电子政务计划的重要组成部分。这一时期还标准化了至今仍在使用的验证算法和格式。
近期发展
近年来,AFIP加强了CUIT/CUIL号码的安全性和验证流程,实施了更复杂的验证系统,并将其与其他政府数据库整合。该系统现在在阿根廷打击税收逃避和正规化经济的努力中扮演着关键角色。
如今,CUIT/CUIL不仅用于税务和就业目的,还用于广泛的活动,包括银行业务、财产交易、公用事业服务和在线购物,使其成为在阿根廷运营的个人和企业的基本标识符。
实施注意事项
在自己的系统中实施CUIT/CUIL验证时,请考虑以下技术方面:
算法稳定性:自1990年代标准化以来,当前的CUIT/CUIL验证算法一直保持不变。根据AFIP的技术文档,模数-11加权校验位方法已在2854/2010号总决议及后续法规中编码。这种稳定性意味着实施很少需要更新,但您应该关注AFIP的任何监管变更公告。
性能优化:验证算法计算开销很小——仅需10次乘法、一次加法和一次模运算。对于每秒处理数千次验证的高吞吐量系统,瓶颈通常是验证AFIP数据库时的网络延迟,而非算法本身。考虑使用适当的TTL(通常为24小时)缓存AFIP验证结果,以平衡新鲜度和性能。
字符编码:从外部来源(PDF、扫描文档、网页抓取)处理CUIT/CUIL数据时,要注意字符编码问题。连字符可能显示为长破折号(U+2014)、短破折号(U+2013)或减号(U+2212),而不是标准连字符减号(U+002D)。在验证前对其进行规范化,以防止误判。
国际化:如果您的应用服务于多个拉丁美洲市场,请注意阿根廷的CUIT/CUIL使用特定权重的模数-11,而智利的RUT使用不同权重的模数-11,巴西的CPF/CNPJ则使用另一种模式的模数-11。不要假定算法相同——每个国家的系统都有独特的规则。要更广泛地了解校验位算法,请参见ISO/IEC 7064标准,该标准规范了各种基于模数的验证方案。
数据库存储:存储不带连字符的CUIT/CUIL号码,使用固定长度的CHAR(11),或者如果数据库支持不丢失精度的11位整数,则使用BIGINT。添加检查约束,在数据库级别验证校验位,作为防止数据损坏的安全网。在此字段上创建索引,以便用户按CUIT/CUIL搜索时能高效查找。
代码示例
Python
1def calculate_verification_digit(type_code, dni):
2 # 转换为字符串并确保DNI为8位数字,前面补零
3 type_code_str = str(type_code)
4 dni_str = str(dni).zfill(8)
5
6 # 组合类型代码和DNI
7 digits = type_code_str + dni_str
8
9 # 每个位置的权重
10 weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
11
12 # 计算乘积之和
13 sum_products = sum(int(digits[i]) * weights[i] for i in range(10))
14
15 # 计算验证数字
16 verification_digit = 11 - (sum_products % 11)
17
18 # 特殊情况
19 if verification_digit == 11:
20 verification_digit = 0
21 elif verification_digit == 10:
22 verification_digit = 9
23
24 return verification_digit
25
26def generate_cuit_cuil(type_code, dni=None):
27 import random
28
29 # 有效的类型代码
30 valid_type_codes = [20, 23, 24, 27, 30, 33, 34]
31
32 if type_code not in valid_type_codes:
33 raise ValueError(f"无效的类型代码。必须是以下之一:{valid_type_codes}")
34
35 # 如果未提供,生成随机DNI
36 if dni is None:
37 dni = random.randint(10000000, 99999999)
38
39 # 计算验证数字
40 verification_digit = calculate_verification_digit(type_code, dni)
41
42 # 格式化CUIT/CUIL
43 return f"{type_code}-{str(dni).zfill(8)}-{verification_digit}"
44
45def validate_cuit_cuil(cuit_cuil):
46 # 如果存在连字符,则删除
47 cuit_cuil_clean = cuit_cuil.replace("-", "")
48
49 # 检查基本格式
50 if not cuit_cuil_clean.isdigit() or len(cuit_cuil_clean) != 11:
51 return False, "无效的格式"
52
53 # 提取部分
54 type_code = int(cuit_cuil_clean[0:2])
55 dni = int(cuit_cuil_clean[2:10])
56 verification_digit = int(cuit_cuil_clean[10])
57
58 # 验证类型代码
59 valid_type_codes = [20, 23, 24, 27, 30, 33, 34]
60 if type_code not in valid_type_codes:
61 return False, "无效的类型代码"
62
63 # 计算并比较验证数字
64 calculated_digit = calculate_verification_digit(type_code, dni)
65 if calculated_digit != verification_digit:
66 return False, "无效的验证数字"
67
68 return True, "有效的CUIT/CUIL"
69
70# 使用示例
71print(generate_cuit_cuil(20, 12345678)) # 为特定DNI生成
72print(generate_cuit_cuil(27)) # 使用随机DNI生成
73print(validate_cuit_cuil("20-12345678-9")) # 验证CUIT/CUIL
74JavaScript
1function calculateVerificationDigit(typeCode, dni) {
2 // 转换为字符串并确保DNI为8位数字,前面补零
3 const typeCodeStr = typeCode.toString();
4 const dniStr = dni.toString().padStart(8, '0');
5
6 // 组合类型代码和DNI
7 const digits = typeCodeStr + dniStr;
8
9 // 每个位置的权重
10 const weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
11
12 // 计算乘积之和
13 let sumProducts = 0;
14 for (let i = 0; i < 10; i++) {
15 sumProducts += parseInt(digits[i]) * weights[i];
16 }
17
18 // 计算验证数字
19 let verificationDigit = 11 - (sumProducts % 11);
20
21 // 特殊情况
22 if (verificationDigit === 11) {
23 verificationDigit = 0;
24 } else if (verificationDigit === 10) {
25 verificationDigit = 9;
26 }
27
28 return verificationDigit;
29}
30
31function generateCuitCuil(typeCode, dni) {
32 // 有效的类型代码
33 const validTypeCodes = [20, 23, 24, 27, 30, 33, 34];
34
35 if (!validTypeCodes.includes(typeCode)) {
36 throw new Error(`无效的类型代码。必须是以下之一:${validTypeCodes.join(', ')}`);
37 }
38
39 // 如果未提供,生成随机DNI
40 if (dni === undefined) {
41 dni = Math.floor(Math.random() * 90000000) + 10000000;
42 }
43
44 // 计算验证数字
45 const verificationDigit = calculateVerificationDigit(typeCode, dni);
46
47 // 格式化CUIT/CUIL
48 return `${typeCode}-${dni.toString().padStart(8, '0')}-${verificationDigit}`;
49}
50
51function validateCuitCuil(cuitCuil) {
52 // 如果存在连字符,则删除
53 const cuitCuilClean = cuitCuil.replace(/-/g, '');
54
55 // 检查基本格式
56 if (!/^\d{11}$/.test(cuitCuilClean)) {
57 return { isValid: false, errorMessage: '无效的格式' };
58 }
59
60 // 提取部分
61 const typeCode = parseInt(cuitCuilClean.substring(0, 2));
62 const dni = parseInt(cuitCuilClean.substring(2, 10));
63 const verificationDigit = parseInt(cuitCuilClean.substring(10, 11));
64
65 // 验证类型代码
66 const validTypeCodes = [20, 23, 24, 27, 30, 33, 34];
67 if (!validTypeCodes.includes(typeCode)) {
68 return { isValid: false, errorMessage: '无效的类型代码' };
69 }
70
71 // 计算并比较验证数字
72 const calculatedDigit = calculateVerificationDigit(typeCode, dni);
73 if (calculatedDigit !== verificationDigit) {
74 return { isValid: false, errorMessage: '无效的验证数字' };
75 }
76
77 return { isValid: true };
78}
79
80// 使用示例
81console.log(generateCuitCuil(20, 12345678)); // 为特定DNI生成
82console.log(generateCuitCuil(27)); // 使用随机DNI生成
83console.log(validateCuitCuil("20-12345678-9")); // 验证CUIT/CUIL
84Java
1import java.util.Arrays;
2import java.util.List;
3import java.util.Random;
4
5public class CuitCuilUtils {
6 private static final List<Integer> VALID_TYPE_CODES = Arrays.asList(20, 23, 24, 27, 30, 33, 34);
7 private static final int[] WEIGHTS = {5, 4, 3, 2, 7, 6, 5, 4, 3, 2};
8
9 public static int calculateVerificationDigit(int typeCode, int dni) {
10 // 转换为字符串并确保DNI为8位数字,前面补零
11 String typeCodeStr = String.valueOf(typeCode);
12 String dniStr = String.format("%08d", dni);
13
14 // 组合类型代码和DNI
15 String digits = typeCodeStr + dniStr;
16
17 // 计算乘积之和
18 int sumProducts = 0;
19 for (int i = 0; i < 10; i++) {
20 sumProducts += Character.getNumericValue(digits.charAt(i)) * WEIGHTS[i];
21 }
22
23 // 计算验证数字
24 int verificationDigit = 11 - (sumProducts % 11);
25
26 // 特殊情况
27 if (verificationDigit == 11) {
28 verificationDigit = 0;
29 } else if (verificationDigit == 10) {
30 verificationDigit = 9;
31 }
32
33 return verificationDigit;
34 }
35
36 public static String generateCuitCuil(int typeCode, Integer dni) {
37 if (!VALID_TYPE_CODES.contains(typeCode)) {
38 throw new IllegalArgumentException("无效的类型代码。必须是以下之一:" + VALID_TYPE_CODES);
39 }
40
41 // 如果未提供,生成随机DNI
42 if (dni == null) {
43 Random random = new Random();
44 dni = 10000000 + random.nextInt(90000000);
45 }
46
47 // 计算验证数字
48 int verificationDigit = calculateVerificationDigit(typeCode, dni);
49
50 // 格式化CUIT/CUIL
51 return String.format("%d-%08d-%d", typeCode, dni, verificationDigit);
52 }
53
54 public static ValidationResult validateCuitCuil(String cuitCuil) {
55 // 如果存在连字符,则删除
56 String cuitCuilClean = cuitCuil.replace("-", "");
57
58 // 检查基本格式
59 if (!cuitCuilClean.matches("\\d{11}")) {
60 return new ValidationResult(false, "无效的格式");
61 }
62
63 // 提取部分
64 int typeCode = Integer.parseInt(cuitCuilClean.substring(0, 2));
65 int dni = Integer.parseInt(cuitCuilClean.substring(2, 10));
66 int verificationDigit = Integer.parseInt(cuitCuilClean.substring(10, 11));
67
68 // 验证类型代码
69 if (!VALID_TYPE_CODES.contains(typeCode)) {
70 return new ValidationResult(false, "无效的类型代码");
71 }
72
73 // 计算并比较验证数字
74 int calculatedDigit = calculateVerificationDigit(typeCode, dni);
75 if (calculatedDigit != verificationDigit) {
76 return new ValidationResult(false, "无效的验证数字");
77 }
78
79 return new ValidationResult(true, null);
80 }
81
82 public static class ValidationResult {
83 private final boolean isValid;
84 private final String errorMessage;
85
86 public ValidationResult(boolean isValid, String errorMessage) {
87 this.isValid = isValid;
88 this.errorMessage = errorMessage;
89 }
90
91 public boolean isValid() {
92 return isValid;
93 }
94
95 public String getErrorMessage() {
96 return errorMessage;
97 }
98 }
99
100 public static void main(String[] args) {
101 // 使用示例
102 System.out.println(generateCuitCuil(20, 12345678)); // 为特定DNI生成
103 System.out.println(generateCuitCuil(27, null)); // 使用随机DNI生成
104 System.out.println(validateCuitCuil("20-12345678-9").isValid()); // 验证CUIT/CUIL
105 }
106}
107PHP
<?php function calculateVerificationDigit($typeCode, $dni) { // 转换为字符串并确保DNI为8位数字,前面补零 $typeCodeStr = (string)$typeCode; $dniStr = str_pad((string)$dni, 8, '0', STR_PAD_LEFT); // 组合类型代码和DNI $digits = $typeCodeStr . $dniStr; // 每个位置的权重 $weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]; // 计算乘积之和 $sumProducts = 0; for ($i = 0; $i < 10; $i++) { $sumProducts += (int)$digits[$i] * $weights[$i]; } // 计算验证数字 $verificationDigit = 11 - ($sumProducts % 11); // 特殊情况 if ($verificationDigit == 11) { $verificationDigit = 0; } else if ($verificationDigit == 10) { $verificationDigit = 9; } return $verificationDigit; } function generateCuitCuil($typeCode, $dni = null) { // 有效的类型代码 $validTypeCodes = [20, 23, 24, 27, 30, 33, 34]; if (!in_array($typeCode, $validTypeCodes)) { throw new Exception("无效的类型代码。必须是以下之一:" . implode(', ', $validTypeCodes)); } // 如果未提供,生成随机DNI if ($dni === null) { $dni = rand(10000000, 99999999); } // 计算验证数字 $verificationDigit = calculateVerificationDigit($typeCode, $dni); // 格式化CUIT/CUIL return sprintf("%d-%08d-%d", $typeCode, $dni, $verificationDigit); } function validateCuitCuil($cuitCuil) { // 如果存在连字符,则删除 $cuitCuilClean = str_replace('-', '', $cuitCuil); // 检查基本格式 if (!preg_match('/^\d{11}$/', $cuitCuilClean)) { return ['isValid' => false, 'errorMessage' => '无效的格式']; } // 提取部分 $typeCode = (int)substr($cuitCuilClean, 0, 2); $dni = (int)substr($cuitCuilClean, 2, 8); $verificationDigit = (int)substr($cuitCuilClean, 10, 1); // 验证类型代码 $validTypeCodes = [20, 23, 24, 27, 30, 33, 34]; if (!in_array($typeCode, $validTypeCodes)) { return ['isValid' => false, 'errorMessage' => '无效的类型代码']; } // 计算并比较验证数字 $calculatedDigit = calculateVerificationDigit($typeCode, $dni); if ($calculatedDigit !== $verificationDigit) { return ['isValid' => false, 'errorMessage' => '无效的验证数字']; } return ['isValid' => true]; } // 使用示例 echo generateCuitCuil(20, 12345678) . "\n"; // 为特定DNI生成 echo generateCuitC ## 常见问题 ### CUIT 和 CUIL 有什么区别? 以下是实际区别:**CUIT**(统一税务识别码)识别向 AFIP 申报税款的纳税人,包括个人和企业。**CUIL**(统一劳动识别码)识别社会保障系统中的工作者,用于就业和退休目的。 实际上,许多个人既有 CUIL(从第一份工作开始),后来可能还会获得 CUIT(如果成为自雇人士或开始创业)。这些数字使用相同的格式和计算规则,因此从验证角度来看,处理方式相同。这种区别对于行政目的很重要:工资系统通常使用 CUIL,而开具发票和税务系统使用 CUIT。 ### 个人和公司使用哪些类型代码? 对于个人: - 20、23、24:男性个人(CUIL) - 27:女性个人(CUIL) 对于公司和组织: - 30:公司(CUIT) - 33:民间社团(CUIT) - 34:基金会(CUIT) ### 校验位是如何计算的? 算法使用加权乘法:取前 10 位数字,分别乘以权重 **5, 4, 3, 2, 7, 6, 5, 4, 3, 2**,求和,然后计算 11 减去(和模 11)。 有两种边界情况会让人困惑:当计算结果为 11 时,使用 0 作为校验位;当结果为 10 时,使用 9。这些特殊情况存在是因为校验位必须是单个数字(0-9)。这是一种模 11 校验位算法,类似于 ISBN-10 码和世界各地其他识别系统使用的算法。 ### 我可以使用这个工具生成真实的、官方的 CUIT/CUIL 号码吗? 不可以——这个工具生成的是数学上有效的测试数据,但这些号码并未在 AFIP 正式注册。可以把它想象成使用 Luhn 算法生成有效的信用卡号:它通过格式验证,但并未连接到真实账户。 对于官方 CUIT/CUIL 注册,个人需要携带身份证(DNI)到 AFIP,而企业必须完成公司注册程序。该工具仅用于开发、测试和教育目的,在需要看起来有效且不会与真实身份号码冲突的数据时使用。 ### 为什么我的 CUIT/CUIL 验证失败,尽管格式看起来是正确的? 当验证失败但格式看起来正确时,请检查以下常见问题: 1. **无效的类型代码**:只有 20、23、24、27、30、33、34 是有效的。使用 21、25 或其他数字将导致失败。 2. **错误的校验位**:最后一位数字必须与算法计算匹配。即使 DNI 部分的一个数字发生变化,也需要重新计算校验位。 3. **格式问题**:必须恰好是 11 位数字(XX-XXXXXXXX-X)。注意额外的空格或缺少的前导零。 4. **隐藏字符**:从 PDF 或网站复制粘贴有时会引入破坏验证的不可见 Unicode 字符。 一个快速调试技巧:删除最后一位数字并手动重新计算。如果不匹配,那就是问题所在。 ### CUIT/CUIL 号码中的连字符是否是必需的? 连字符仅用于显示格式——在计算时不是号码的一部分。当你看到 **20-12345678-9** 时,算法只关心 11 位数字:**20123456789**。 大多数验证实现在处理前会去除连字符,这就是为什么我们的工具接受 **20-12345678-9** 和 **20123456789**。在 API 和数据库中,你通常会存储无连字符的版本,并仅在向用户显示时添加连字符。这种方法可以防止格式不一致导致验证失败。 ### CUIT/CUIL 号码的 DNI 部分可以少于 8 位数字吗? DNI 部分必须恰好是 8 位数字——没有例外。如果某人的实际 DNI 位数较少(这在较早的、编号较低的 DNI 中会发生),则用前导零填充。 例如,DNI **1234567** 在 CUIT/CUIL 结构中变为 **01234567**。这是在 DNI 和 CUIT/CUIL 格式之间转换时常见的 bug 源。我见过系统验证失败,因为它们忘记填充,将 **1234567** 视为 7 位数字而不是 **01234567**。在代码中始终使用零填充函数,而不是简单的字符串连接。 ### 如何验证 CUIT/CUIL 是否在阿根廷正式注册? 我们的工具验证数学正确性——格式、类型代码和校验位。但它无法判断号码是否真的在 AFIP 注册,或是否属于真实的人或企业。 对于官方验证,请使用 [AFIP 的网络服务](https://www.afip.gob.ar/)或他们的"注册证明"查询工具。AFIP 提供用于自动验证的 API,尽管你需要注册为开发者并进行身份验证。在生产环境中,实施两层验证:首先进行算法验证(快速,捕获打字错误和格式错误),然后针对 AFIP 数据库验证(较慢,但确认号码真实且有效)。 ### 我可以在商业应用中使用这个工具吗? 可以——CUIT/CUIL 验证算法是 AFIP 定义的公共标准,因此你可以在商业软件中自由实施。这里提供的代码示例旨在作为参考实现。 对于生产使用,请考虑以下最佳实践:为边界情况实施适当的错误处理,为调试验证失败添加日志记录,针对 AFIP 的官方数据库进行验证(不仅仅是算法验证),并在 AFIP 更改规则时保持实施更新(尽管当前算法已经稳定数十年)。 ### 工具是否存储生成或验证的 CUIT/CUIL 号码? 不会——所有处理都在你的浏览器(客户端)中进行。当你生成或验证 CUIT/CUIL 时,计算在本地使用 JavaScript 运行。不会向我们的服务器发送任何内容,我们也不会记录、存储或跟踪你输入或生成的任何号码。 这种客户端方法确保隐私,并且意味着工具在页面加载后可以离线工作。你可以通过打开浏览器的开发者工具并观察网络选项卡来验证这一点——在生成或验证号码时,你会看到没有发送任何请求。 ## 参考文献 1. AFIP(公共收入联邦管理局)。"CUIT/CUIL/CDI"。官方网站。[https://www.afip.gob.ar/](https://www.afip.gob.ar/) 2. 劳动、就业和社会保障部。"CUIL - 劳动唯一识别码"。[https://www.argentina.gob.ar/trabajo](https://www.argentina.gob.ar/trabajo) 3. ANSES(国家社会保障管理局)。"获取我的CUIL"。[https://www.anses.gob.ar/](https://www.anses.gob.ar/) 4. 阿根廷共和国官方公报。"AFIP总决议2854/2010:程序。税务唯一识别码(C.U.I.T.)"。 5. 阿根廷共和国财政法。"纳税人识别和登记"。 --- 无论您是在构建发票系统、测试员工入职流程,还是学习验证算法,这个工具都能提供您所需的CUIT/CUIL生成和验证。生成通过算法验证的测试数据,在数据库中使用前验证数字,或探索实际中的校验位算法是如何工作的。